
  <rss xmlns:content="http://purl.org/rss/1.0/modules/content/" version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title><![CDATA[Bruno Sabot RSS Feed]]></title>
    <description><![CDATA[I am Bruno Sabot, a Front-end developer currently living in Bordeaux, France.]]></description>
    <link>https://brunosabot.dev/</link>
    <generator>NextJS</generator>
    <lastBuildDate>Mon, 11 May 2026 18:13:15 GMT</lastBuildDate>
    <atom:link href="https://brunosabot.dev/rss.xml" rel="self" type="application/rss+xml" />
    
      <item>
        <title><![CDATA[Streamline Your Home Assistant UI with Streamline Card]]></title>
        <description><![CDATA[Reduce Lovelace repetition, create dynamic dashboards, and manage your configuration efficiently with this modern templating card.]]></description>
        <link>https://brunosabot.dev/posts/2025/streamline-your-home-assistant-ui-with-streamline-card/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2025/streamline-your-home-assistant-ui-with-streamline-card/</guid>
        <pubDate>Mon, 28 Apr 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Hey everyone,


I want to talk about **Streamline Card**, a project I first released back in September 2024! It's a custom card for Home Assistant designed to make managing your Lovelace dashboards much easier, and it's been evolving ever since.


# Why Do We Even Need This?


If you've spent any time setting up your Home Assistant dashboard (Lovelace), you've probably run into this: repetition. You create a nice card for one light, then you copy and paste the code, change the entity ID and name, and repeat... and repeat... for every single light, sensor, or switch.


This copy-paste routine works, but it has downsides:

- **It's Tedious:** Setting up lots of similar cards takes time and effort.
- **It's Error-Prone:** It's easy to forget to change an entity ID or make a typo somewhere.
- **It's Hard to Maintain:** Decided you want to change the style or add a feature to all your light cards? You have to go back and edit _every single one_ individually.

Streamline Card tackles this head-on by letting you create **reusable templates** for your cards. Define it once, use it everywhere. This drastically **reduces redundancy**, makes your configuration **easier to maintain**, and **lowers the chance of copy-paste errors**.


# Why Streamline Card? (And Not Just Use `decluttering-card`?)


Some of you might know `decluttering-card`, which offered a similar templating feature. I used it myself for a long time! However, the project seems unmaintained now, with questions and issues piling up.


While I enjoy contributing to community projects (like I did with `Bubble-Card`), the lack of activity on `decluttering-card` and my desire for some extra features led me to build Streamline Card from the ground up.


It keeps the core templating idea but adds several new capabilities:

- **Template System:** The foundation for reusing card configurations.
- **Visibility Option Support:** Integrates with Home Assistant's built-in conditional visibility. _(New!)_
- **New Section Layout Support:** Works seamlessly with the latest dashboard layout options in Home Assistant. _(New!)_
- **Javascript Template Support:** Allows for powerful dynamic logic _within_ your templates. _(New!)_
- **UI Editor:** Provides a user-friendly interface for creating and managing your templates, as an alternative to YAML. _(New!)_

So, it's built to solve the original problem but updated for modern Home Assistant and with more flexibility.


# How It Works: The Magic of Templates


The concept behind Streamline Card is straightforward:

- **Create a Template:** You define the structure and appearance of a card (like a `custom:button-card` or any other card type) in a central place. You use special placeholders, like `[[entity]]` or `[[name]]`, for the parts that will change for each specific card. You can also set default values for these variables.
- **Reuse the Template:** When adding a card to your dashboard, instead of writing out the full card configuration, you just reference the template name and provide the specific values for the variables (e.g., `entity: light.living_room_main`, `name: 'Main Light'`).
- **Update Centrally:** If you want to change something about all the cards using that template (e.g., change the icon style), you only need to edit the template definition itself. All the cards using it will update automatically.

Plus, with **Javascript support**, you can embed small scripts directly into your templates. This allows you to dynamically change how a card looks or behaves based on entity states or other conditions, often simplifying things that might otherwise require complex `card-mod` configurations.


# Setting Up and Using Templates


Getting Streamline Card into your Home Assistant is easy:

- **HACS (Recommended):** Search for "Streamline Card" in the Home Assistant Community Store and install it from there.
- **Manual Install:** Download the `streamline-card.js` file from the [latest release on GitHub](https://github.com/brunosabot/streamline-card/releases) and place it in your `config/www` directory. Then, add it as a resource in Home Assistant (either via the UI or `lovelace-resources.yaml`).

(See the [GitHub repo](https://github.com/brunosabot/streamline-card?tab=readme-ov-file#installation) for detailed steps.)


Once installed, you define your templates. You can do this in two ways:

- **YAML:** Add a `streamline_template:` section to your main Lovelace configuration file (or a file included from it).
- **UI Editor:** Use the configuration screen provided by Streamline Card in the Home Assistant UI.

Here&#8217;s a basic example of defining a template in YAML:


```yaml
streamline_template:
  # Template definitions go here
  my_light_template: # A unique name for your template
    default: # Optional: Define default values for variables
      - icon: mdi:lightbulb
    card: # The actual card configuration using variables
      type: custom:bubble-card # Use any card type you like, here Bubble Card
      card_type: button # Example: using a button inside Bubble Card
      entity: '[[entity]]' # Variables are enclosed in [[...]]
      name: '[[name]]'
      icon: '[[icon]]'
      # ... add any other Bubble Card options here
```


And here&#8217;s how you&#8217;d use that template in your dashboard UI configuration:


```yaml
# Card using the template for the Kitchen Light
- type: custom:streamline-card
  template: my_light_template # Reference the template name
  variables: # Provide values for the variables
    entity: light.kitchen_main
    name: Kitchen Light
    icon: mdi:ceiling-light # This overrides the default icon

# Card using the template for the Bedroom Lamp
- type: custom:streamline-card
  template: my_light_template
  variables:
    entity: light.bedroom_lamp
    name: Bedroom Lamp # This will use the default icon (mdi:lightbulb)
```


Much cleaner and easier to manage than repeating the full `bubble-card` config!


# Examples in Action


Let's illustrate with a couple of use cases:


**1. Simple Light Card (The Classic)**


This is the bread-and-butter usage. Create a template (`my_light_template` like above) for how you want all your standard light switches/buttons to look. Maybe it's a `custom:bubble-card` (like the example), `custom:button-card`, `custom:mushroom-light-card`, or even a standard `entities` card row. Define variables for `entity`, `name`, maybe `icon` or `area`. Then, simply add `custom:streamline-card` entries to your dashboard, specifying the template and the entity/name for each light.


Here's the YAML again for this specific example:

- _First, define the template (e.g., in your_ _`configuration.yaml`_ _or a dedicated template file):_

    ```yaml
    streamline_template:
      my_light_template:
        default:
          - icon: mdi:lightbulb
        card:
          type: custom:bubble-card
          card_type: button
          entity: '[[entity]]'
          name: '[[name]]'
          icon: '[[icon]]'
    ```

- _Then, use it in your Lovelace dashboard configuration:_

    ```yaml
    # Kitchen Light
    - type: custom:streamline-card
      template: my_light_template
      variables:
        entity: light.kitchen_main
        name: Kitchen Light
        icon: mdi:ceiling-light
    
    # Bedroom Lamp
    - type: custom:streamline-card
      template: my_light_template
      variables:
        entity: light.bedroom_lamp
        name: Bedroom Lamp
    ```


**2. Dynamic Entities List (Using Javascript)**


Want to get a bit fancier? You can use Javascript _inside_ your template's card definition to dynamically generate content. This example shows how to create an `entities` card where the rows are generated based on a list passed as a variable.

- First, define a template that expects a variable named `list`. This list should contain objects, each with an `entity` and `name`. The `card` section uses `entities_javascript` to process this list.

    _Here's how the_ _`card`_ _section of the template definition might look:_


    ```yaml
    streamline_template:
      my_dynamic_list_template:
        # No defaults needed here, the list variable is mandatory
        card:
          type: entities
          # Use Javascript to generate the entities based on the 'list' variable
          entities_javascript: |
            // Ensure the 'list' variable exists and is an array
            if (!variables.list || !Array.isArray(variables.list)) {
              return [{ type: 'section', label: 'Error: Invalid list variable' }];
            }
            // Map each item in the list to an entity row configuration
            return variables.list.map(({entity, name}) =&#62; ({
              entity: entity,
              name: name,
              secondary_info: 'last-changed', // Example: Add secondary info
              state_color: true // Example: Enable state coloring
            }));
    ```

- Then, use this template in your dashboard, providing the `list` variable containing your desired entities.

    ```yaml
    - type: custom:streamline-card
      template: my_dynamic_list_template
      variables:
        # Provide the list of entities to display
        list:
          - entity: sensor.temperature_outside
            name: Outdoor Temp
          - entity: sensor.humidity_outside
            name: Outdoor Humidity
          - entity: light.living_room_main
            name: Living Room Light
          # Add more entities as needed
    ```


This approach allows you to pass complex data structures to your templates and use Javascript to render them dynamically within standard Home Assistant cards, keeping your dashboard configuration clean.


# Try It Out and Share Your Thoughts!


Streamline Card is still evolving, so you might encounter the occasional bug or quirk. However, if you're looking to clean up your Lovelace configuration, reduce repetition, and make future updates easier, I encourage you to give it a try!


You can find all the code, documentation, and examples on the [**Streamline Card GitHub Repository**](https://github.com/brunosabot/streamline-card).


Please feel free to:

- Report any bugs you find by opening an issue.
- Suggest new features or improvements.
- Share your own cool templates or use cases!

Your feedback is invaluable in making Streamline Card better.


Thanks for reading, and happy streamlining!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Mastering Mocking in Vitest: Beyond ]]></title>
        <description><![CDATA[Master Vitest mocking! Learn advanced techniques beyond vi.mock() for robust, reliable JavaScript/TypeScript tests. SpyOn, vi.fn, and more!]]></description>
        <link>https://brunosabot.dev/posts/2025/mastering-mocking-in-vitest-beyond-vi-mock/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2025/mastering-mocking-in-vitest-beyond-vi-mock/</guid>
        <pubDate>Thu, 06 Mar 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Introduction: The Why and When of Mocking


## Setting the Stage: Why Mocking Matters


Imagine you're building a website for the Trade Federation, those folks from Star Wars. They're selling goods to both the Empire and the Rebels. Now, their website has a complex system for processing orders. It talks to a database, sends emails, and even checks stock levels with a remote API.


Here's the problem: when you want to test just _one_ part of this system, say, the order processing logic, you don't want to rely on the database, the email service, or the external API. Why?

- **Slow Tests:** Talking to real external services takes time. Your tests will be slow, and you'll be waiting a long time to see if your code works.
- **Unreliable Tests:** If the database or API is down, your tests will fail, even if your code is perfect. This is called "flakiness," and it's a real headache.
- **Side Effects:** Sending real emails or changing a real database can have unwanted side effects. You don't want to accidentally send a thousand emails to the Empire while testing!

That's where mocking comes in. Mocking means creating fake versions of these external dependencies. We replace the real database with a fake one, the real email service with a fake one, and so on. This isolates the part of your code you're testing, so you can focus on it without distractions.


**Example:**


```typescript
// OrderProcessor.ts
import { Database } from './database';
import { EmailService } from './emailService';
import { StockAPI } from './stockAPI';

export class OrderProcessor {
  constructor(
    private database: Database,
    private emailService: EmailService,
    private stockAPI: StockAPI
  ) {}

  async processOrder(order: any) {
    await this.stockAPI.checkStock(order.productId);
    await this.database.saveOrder(order);
    await this.emailService.sendConfirmation(order.customerEmail);

    return 'Order processed';
  }
}
```


When testing `OrderProcessor`, we don't want to rely on real `Database`, `EmailService`, or `StockAPI`. We'll mock them!


## The Pitfalls of Over-Mocking: Don't Lose Sight of Reality


Mocking is powerful, but it's easy to go overboard. If you mock _everything_, your tests might not reflect how your code works in the real world.


Imagine you mock the entire order processing system, including the business logic. Your tests might pass, but if there's a bug in the real logic, you won't catch it.


It's about finding the right balance. Mock external dependencies, but don't mock the core logic of the unit you're testing unless it is absolutely necessary.


Think of it this way: you want to test if the Trade Federation's order processor works correctly. But you don't want to test if the entire galaxy is working correctly.


**Example:**


If you mock the `processOrder` function itself, you will not test the logic inside of it, and the test is useless.


```typescript
// OrderProcessor.test.ts
// Don't do this (unless you have a very good reason!)
vi.mock('./OrderProcessor', () =&#62; ({
  OrderProcessor: vi.fn().mockImplementation(() =&#62; ({
    processOrder: vi.fn().mockResolvedValue('Mocked order processed'),
  })),
}));
```


## Introducing Fixtures: Setting Up a Consistent Stage


In the world of testing, a "fixture" is like a stage set. It's a way to create a consistent starting point for your tests.


Imagine you're testing the Trade Federation's inventory system. You need to create some sample products to test with. Instead of creating these products in every test, you can create a fixture.


**Example: Object Fixture**


```typescript
// __fixtures__/product.fixtures.ts
export const productFixture = {
  productId: '123',
  name: 'Droid Parts',
  price: 100,
};
```


Then, in your test:


```typescript
// discount.test.ts
import { expect, test } from 'vitest';
import { productFixture } from './__fixtures__/product.fixtures';
import { applyDiscount } from './discount';

test('should apply a discount to a product price', () =&#62; {
  // Arrange
  const discountedPrice = applyDiscount(productFixture.price, 20);

	// Assert
  expect(discountedPrice).toBe(80);
});
```


**Example: Function Fixture**


You can also use functions to create more complex fixtures:


```typescript
// __fixtures__/order.fixtures.ts
export const createOrderFixture = (productId: string, quantity: number) =&#62; ({
  orderId: 'order-66',
  productId,
  quantity,
  customerEmail: 'rebel@alliance.com',
});
```


And in your test:


```typescript
// order.test.ts
import { expect, test } from 'vitest';
import { createOrderFixture } from './__fixtures__/order.fixtures.ts';
import { productFixture } from './__fixtures__/product.fixtures.ts';
import { calculateTotal } from './order.ts';

test('should calculate the total price of an order', () =&#62; {
  // Arrange
  const order = createOrderFixture('123', 5);
  const total = calculateTotal(order.quantity, productFixture.price);

	// Assert
  expect(total).toBe(500);
});
```


Using fixtures makes your tests:

- **DRY (Don't Repeat Yourself):** You avoid repeating the same setup code.
- **Readable:** Your tests are cleaner and easier to understand.
- **Maintainable:** If you need to change the setup, you only change it in one place.

# Core Mocking Techniques: A Deep Dive


## `vi.spyOn()`: Being a Code Detective (and Letting the Suspect Act)


Imagine you need to keep tabs on a function, but you don't want to stop it from doing its job. That's the beauty of `vi.spyOn()`. It's like placing a wiretap&#8212;you're listening in, but the conversation keeps going. You can see what a function is doing, how many times it's called, and what arguments it receives, all while letting it execute its actual logic.


Let's say the Trade Federation has a `ShipmentProcessor` class:


```typescript
// shipment.ts
export class ShipmentProcessor {
  sendShipment(orderId: string, address: string) {
    console.log(`Sending shipment ${orderId} to ${address}`);
    // ... some real shipping logic ...
    return `Shipment ${orderId} sent`;
  }
}
```


Here's how you'd spy on the `sendShipment` method:


```typescript
// shipment.test.ts
import { vi, expect, test } from 'vitest';
import { ShipmentProcessor } from './shipment';

test('should call sendShipment with correct arguments and return the correct value', () =&#62; {
  // Arrange
  const processor = new ShipmentProcessor();
  const spy = vi.spyOn(processor, 'sendShipment');

  const orderId = 'order-66';
  const address = 'Coruscant';

  // Act
  const result = processor.sendShipment(orderId, address);

  // Assert
  expect(spy).toHaveBeenCalledWith(orderId, address);
  expect(spy).toHaveBeenCalledTimes(1);
  expect(result).toBe(`Shipment ${orderId} sent`); // Check the returned value

  spy.mockRestore(); // Clean up the spy!
});
```


Notice how we're also checking the returned value of `sendShipment`. This proves that the actual function logic is executed. Remember, `vi.spyOn()` lets the function run its course while you observe.


## **`vi.fn()`****: Building Your Own Fake Functions (With Care)**


Now, sometimes you need to completely replace a function with a fake one. That's where `vi.fn()` comes in. It's like building a robot that mimics a real person. You get to decide exactly what the function returns, what errors it throws, or what logic it runs.


But here's the catch: you need to be careful. If your fake function doesn't behave similarly to the real one, your tests might not be relevant. You need to mimic the real function's behavior to some extent.


Let's look at an example with a database:


```typescript
// database.ts
export class Database {
  async saveOrder(data: any) {
    // ... real database logic ...
    return 'Data saved';
  }
}
```


```typescript
// OrderProcessor.ts
import { Database } from './database';
import { EmailService } from './emailService';
import { StockAPI } from './stockAPI';

export class OrderProcessor {
  constructor(
    private database: Database,
    private emailService: EmailService,
    private stockAPI: StockAPI
  ) {}

  async processOrder(order: any) {
    await this.stockAPI.checkStock(order.productId);
    await this.database.saveOrder(order);
    await this.emailService.sendConfirmation(order.customerEmail);

    return 'Order processed';
  }
}
```


```typescript
// orderProcessor.test.ts
import { createOrderFixture } from './__fixtures__/order.fixtures';
import { OrderProcessor } from './orderProcessor';
import { vi, expect, test } from 'vitest';

test('should save order to database', async () =&#62; {
  // Arrange
  const mockDatabase = { saveOrder: vi.fn() };
  const mockEmailService = { sendConfirmation: vi.fn() };
  const mockStockAPI = { checkStock: vi.fn() };

  const processor = new OrderProcessor(mockDatabase, mockEmailService, mockStockAPI);

  const order = createOrderFixture('123', 5);

  // Act
  await processor.processOrder(order);

  // Assert
  expect(mockDatabase.saveOrder).toHaveBeenCalledWith(order);
});

test('should use mockImplementationOnce to control the return value of a function for a single call', () =&#62; {
  // Arrange
  const mockFunction = vi.fn();
  mockFunction.mockImplementationOnce(()=&#62; 'first call');
  mockFunction.mockImplementationOnce(()=&#62; 'second call');

  // Act / Assert
  expect(mockFunction()).toBe('first call');
  expect(mockFunction()).toBe('second call');
  expect(mockFunction()).toBe(undefined);
})

test('should use mockReturnValueOnce to control the return value of a function for a single call', () =&#62; {
  // Arrange
  const mockFunction = vi.fn();
  mockFunction.mockReturnValueOnce('first call');
  mockFunction.mockReturnValueOnce('second call');

  // Act / Assert
  expect(mockFunction()).toBe('first call');
  expect(mockFunction()).toBe('second call');
  expect(mockFunction()).toBe(undefined);
})
```


## **Manual Mocks: Taking Full Control (with** **`vi.fn()`****)**


When `vi.mock()` and `vi.fn()` aren't enough, you need to create a completely custom mock for a module. This is where manual mocks come in, and you can even use `vi.fn()` within them.


Let's say you have a module that reads data from a file:


```typescript
// fileReader.ts
import * as fs from 'fs';

export function readData(filePath: string) {
  return fs.readFileSync(filePath, 'utf-8');
}
```


Create a `__mocks__` directory and a `fileReader.ts` file inside:


```typescript
// __mocks__/fileReader.ts
import { vi } from 'vitest';

export const readData = vi.fn((filePath: string) =&#62; {
  if (filePath === 'test.txt') {
    return 'Mocked data';
  }
  return '';
});
```


Now, in your test:


```typescript
// fileReader.test.ts
import { readData } from './fileReader';
import { vi, expect, test } from 'vitest';

// This line will automatically replace every import of 'fileReader' with the mocked version
vi.mock('./fileReader');

test('should use mocked file data', () =&#62; {
  // Arrange / Act
  const data = readData('test.txt');

  // Assert
  expect(data).toBe('Mocked data');
  expect(readData).toHaveBeenCalledWith('test.txt');
  expect(readData).toHaveBeenCalledTimes(1);
});
```


Using `vi.fn()` in your manual mock gives you the ability to check call counts and arguments, adding even more control and precision to your tests.


# Advanced Mocking Scenarios


## Mocking Asynchronous Functions and Promises: Taming the Async Beast


In the world of JavaScript, asynchronous code is everywhere. Dealing with promises and `async/await` in tests can be tricky, but Vitest provides powerful tools to make it easier.


### Mocking with `mockResolvedValue()` and `mockRejectedValue()`


Imagine you have a function that fetches data from an API:


```typescript
// apiService.ts
export class ApiService {
  async fetchData(url: string) {
    const response = await fetch(url);
    if (!response.ok) {
      throw new Error(`HTTP error! status: ${response.status}`);
    }
    return response.json();
  }
}
```


Here's how you'd mock it:


```typescript
// apiService.test.ts
import { ApiService } from './apiService';
import { vi, expect, test } from 'vitest';

test('should resolve with data', async () =&#62; {
  // Arrange
  const apiService = new ApiService();
  const fetchDataSpy = vi.spyOn(apiService, 'fetchData');
  fetchDataSpy.mockResolvedValue({ message: 'Data received' });

  // Act
  const data = await apiService.fetchData('https://example.com/data');

  // Assert
  expect(data).toEqual({ message: 'Data received' });
  expect(fetchDataSpy).toHaveBeenCalledWith('https://example.com/data');
});

test('should reject with an error', async () =&#62; {
  // Arrange
  const apiService = new ApiService();
  const fetchDataSpy = vi.spyOn(apiService, 'fetchData');
  fetchDataSpy.mockRejectedValue(new Error('Network error'));

  // Act / Assert
  await expect(apiService.fetchData('https://example.com/data')).rejects.toThrow('Network error');
  expect(fetchDataSpy).toHaveBeenCalledWith('https://example.com/data');
});
```


### Complex Asynchronous Mocks with `mockImplementation(async () =&#62; {...})`


For more complex scenarios, you can use `mockImplementation` with an `async` function:


```typescript
test('should handle complex async logic', async () =&#62; {
  // Arrange
  const apiService = new ApiService();
  const mockFetchData = vi.spyOn(apiService, 'fetchData').mockImplementation(async (url: string) =&#62; {
    if (url === 'https://example.com/special') {
      return { special: true };
    }
    return { normal: true };
  });

  // Act
  const specialData = await apiService.fetchData('https://example.com/special');
  const normalData = await apiService.fetchData('https://example.com/normal');

  // Assert
  expect(specialData).toEqual({ special: true });
  expect(normalData).toEqual({ normal: true });
  mockFetchData.mockRestore();
});
```


## Mocking External APIs: Building a Fake Galaxy


When your code interacts with external APIs, you need to mock those calls to keep your tests fast and reliable.


### Using `fetch` Mocks


As shown in the previous examples, mocking `fetch` is a common way to handle API calls. You can control the responses and simulate different scenarios.


### Using Mock Service Worker (MSW)


For more complex API mocking, consider using MSW. It lets you intercept network requests at the network level, providing a realistic mocking experience.


```typescript
// __msw__/handlers.ts
import { http, HttpResponse } from 'msw';

export const handlers = [
  http.get('https://api.example.com/products', () =&#62; {
    return HttpResponse.json([{ id: 1, name: 'Droid Parts' }]);
  }),
  http.post('https://api.example.com/orders', () =&#62; {
    return HttpResponse.json({ orderId: 'order-66' });
  }),
];
```


```typescript
// api.test.ts
import { setupServer } from 'msw/node';
import { afterAll, afterEach, beforeAll, expect, test } from 'vitest';
import { handlers } from './__msw__/handlers';
import { fetchProducts, createOrder } from './api.ts';

const server = setupServer(...handlers);

beforeAll(() =&#62; server.listen());
afterEach(() =&#62; server.resetHandlers());
afterAll(() =&#62; server.close());

test('should fetch products', async () =&#62; {
  const products = await fetchProducts();
  expect(products).toEqual([{ id: 1, name: 'Droid Parts' }]);
});

test('should create an order', async () =&#62; {
  const order = await createOrder({ productId: 1, quantity: 2 });
  expect(order).toEqual({ orderId: 'order-66' });
});
```


## Mocking Browser APIs in Node.js: Bringing the Browser to Node


Sometimes, your code might run in a Node.js environment but still rely on browser-specific APIs like `window` or `localStorage`. This is common in frameworks that support server-side rendering or when you're using libraries that make use of browser globals. In these cases, you need to mock those APIs to ensure your tests run correctly in Node.js.


### Using `vi.stubGlobal()`


`vi.stubGlobal()` lets you replace global objects with mock values. This is useful for simple mocks. Let's create a Star Wars example:


```typescript
// planetStorage.ts
export function saveLastVisitedPlanet(planet: string) {
  localStorage.setItem('lastVisitedPlanet', planet);
}

export function getLastVisitedPlanet() {
  return localStorage.getItem('lastVisitedPlanet');
}
```


```typescript
// planetStorage.test.ts
import { saveLastVisitedPlanet, getLastVisitedPlanet } from './planetStorage';
import { vi, expect, test } from 'vitest';

test('should mock localStorage for planet storage', () =&#62; {
  // Arrange
  const mockLocalStorage = {
    store: {},
    setItem: vi.fn((key, value) =&#62; (mockLocalStorage.store[key] = value)),
    getItem: vi.fn((key) =&#62; mockLocalStorage.store[key]),
  };

  vi.stubGlobal('localStorage', mockLocalStorage);

  // Act
  saveLastVisitedPlanet('Tatooine');
  const lastPlanet = getLastVisitedPlanet();

  // Assert
  expect(mockLocalStorage.setItem).toHaveBeenCalledWith('lastVisitedPlanet', 'Tatooine');
  expect(lastPlanet).toBe('Tatooine');
});
```


## Using `jsdom`


For more complex browser environments, use `jsdom`. It creates a simulated browser environment in Node.js, allowing you to test code that relies on the `document` object or other browser APIs.


```typescript
// documentUtil.ts
export function getTitle() {
  return document.title;
}
```


```typescript
// documentUtil.test.ts
import { expect, test, beforeAll } from 'vitest';
import { JSDOM } from 'jsdom';
import { getTitle } from './documentUtil';

beforeAll(() =&#62; {
  const dom = new JSDOM('&#60;!DOCTYPE html&#62;&#60;title&#62;Trade Federation Page&#60;/title&#62;');
  global.document = dom.window.document;
});

test('should mock document.title', () =&#62; {
  expect(getTitle()).toBe('Trade Federation Page');
});
```


## Mocking Classes: Shaping the Object World


Mocking classes is essential when your code depends on complex objects. It allows you to isolate the unit under test and control the behavior of its dependencies.


### Mocking Class Methods


Use `vi.spyOn()` or `vi.fn()` to mock class methods. This is useful when you want to observe or modify the behavior of specific methods.


```typescript
// droid.ts
export class Droid {
  speak() {
    return 'Beep boop';
  }
}
```


```typescript
// droid.test.ts
import { Droid } from './droid';
import { vi, expect, test } from 'vitest';

test('should mock droid speak', () =&#62; {
  const droid = new Droid();
  const speakSpy = vi.spyOn(droid, 'speak').mockReturnValue('Mocked beep');
  expect(droid.speak()).toBe('Mocked beep');
  expect(speakSpy).toHaveBeenCalled();
});
```


### Mocking Class Constructors


Use `vi.mock()` to mock class constructors. This is useful when you want to replace the entire class with a mock implementation.


```typescript
// communicator.ts
import { Droid } from "./droid";

export class Communicator {
  constructor(private droid: Droid) {}
  communicate() {
    return this.droid.speak();
  }
}
```


```typescript
// communicator.test.ts
import { Communicator } from './communicator';
import { Droid } from './droid';
import { vi, expect, test, Mock } from 'vitest';

vi.mock('./droid');

test('should mock droid constructor', () =&#62; {
  vi.mocked(Droid).mockImplementation(() =&#62; ({
    speak: vi.fn().mockReturnValue('Mocked constructor beep'),
  }));

  const communicator = new Communicator(new Droid());
  expect(communicator.communicate()).toBe('Mocked constructor beep');
});
```


# Verification and Assertion Techniques: Proving Your Tests Right


Mocking is only half the battle. Once you've set up your mocks, you need to verify that they're being used correctly. This is where Vitest's assertion techniques come in. Let's dive into how to use them effectively.


## `toHaveBeenCalledTimes()`: Ensuring Function Calls and Catching Unexpected Behavior


Verifying that a function was called, and how many times, is crucial. It ensures that your code is executing the expected logic. While `toHaveBeenCalled()` simply checks if a function was called at least once, `toHaveBeenCalledTimes()` provides a more precise verification.


Imagine you have a `DroidCommunicator` that sends messages:


```typescript
// droidCommunicator.ts
export class DroidCommunicator {
  sendMessage(message: string, droidId: string) {
    console.log(`Sending message "${message}" to droid ${droidId}`);
    // ... actual communication logic ...
  }
}
```


Here's how you'd verify the function calls:


```typescript
// droidCommunicator.test.ts
import { DroidCommunicator } from './droidCommunicator';
import { vi, expect, test } from 'vitest';

test('should call sendMessage exactly once', () =&#62; {
  // Arrange
  const communicator = new DroidCommunicator();
  const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');

  // Act
  communicator.sendMessage('Hello there!', 'R2-D2');

  // Assert
  expect(sendMessageSpy).toHaveBeenCalledTimes(1);
});

test('should call sendMessage multiple times', () =&#62; {
  // Arrange
  const communicator = new DroidCommunicator();
  const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');

  // Act
  communicator.sendMessage('Message 1', 'C-3PO');
  communicator.sendMessage('Message 2', 'C-3PO');

  // Assert
  expect(sendMessageSpy).toHaveBeenCalledTimes(2);
});
```


Now, let's explore a situation where `toHaveBeenCalledTimes()` helps you catch unexpected behavior. Suppose your `DroidCommunicator` has a feature where it should only send a message once, even if the user tries to send it multiple times in quick succession:


```typescript
// droidCommunicator.ts
export class DroidCommunicator {
  private messageSent = false;

  constructor(private logger: (text: string) =&#62; void) {}

  sendMessage(message: string, droidId: string) {
    if (!this.messageSent) {
      this.logger(`Sending message "${message}" to droid ${droidId}`);
      // ... actual communication logic ...
      this.messageSent = true;
    }
  }
}
```


```typescript
test('should only send message once, even if called multiple times', () =&#62; {
  // Arrange
  const logger = vi.fn();
  const communicator = new DroidCommunicator(logger);

  // Act
  communicator.sendMessage('Urgent Message', 'BB-8');
  communicator.sendMessage('Urgent Message', 'BB-8');
  communicator.sendMessage('Urgent Message', 'BB-8');

  // Assert
  expect(logger).toHaveBeenCalledTimes(1);
});
```


In this example, `toHaveBeenCalledTimes(1)` ensures that the message is only sent once, even though `sendMessage` is called multiple times. This helps catch potential bugs and improves the robustness of your code by preventing unintended side effects.


## `toHaveBeenCalledWith()` and `toHaveBeenLastCalledWith()`: Checking Arguments


Verifying the arguments passed to mock functions is essential to ensure that your code is passing the correct data. `toHaveBeenCalledWith()` checks if the mocked function was called with the specific arguments at least once, while `toHaveBeenLastCalledWith()` checks the arguments of the last call.


```typescript
test('should call sendMessage with specific arguments', () =&#62; {
  // Arrange
  const communicator = new DroidCommunicator(console.log);
  const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');

  // Act
  communicator.sendMessage('May the Force be with you', 'BB-8');

  // Assert
  expect(sendMessageSpy).toHaveBeenCalledWith('May the Force be with you', 'BB-8');
});

test('should check the last call arguments', () =&#62; {
    // Arrange
    const communicator = new DroidCommunicator(console.log);
    const sendMessageSpy = vi.spyOn(communicator, 'sendMessage');

    // Act
    communicator.sendMessage('Message 1', 'C-3PO');
    communicator.sendMessage('Message 2', 'R2-D2');

    // Assert
    expect(sendMessageSpy).toHaveBeenLastCalledWith('Message 2', 'R2-D2');
});
```


### Why Check Arguments?


Checking arguments is crucial because it ensures that your mocks are being called with the correct data. Without this, your tests might pass, but your code might still have bugs. For example, if you're mocking an API call, you need to make sure that the correct parameters are being sent to the API.


## Custom Matchers (Optional): Extending Vitest's Assertions


For more complex assertions, you can create custom matchers. This is useful when you have specific assertion logic that you want to reuse across multiple tests.


Here's a simple example:


```typescript
// __helpers__/customMatchers.ts
import { expect } from 'vitest';

expect.extend({
  toBeDroidId(received, expected) {
    const pass = received.startsWith('C');
    if (pass) {
      return {
        message: () =&#62; `expected ${received} not to be a droid ID`,
        pass: true,
      };
    } else {
      return {
        message: () =&#62; `expected ${received} to be a droid ID`,
        pass: false,
      };
    }
  },
});
```


```typescript
// vitest.d.ts
import 'vitest';

interface CustomMatchers&#60;R = unknown&#62; {
  toBeDroidId: () =&#62; R
}

declare module 'vitest' {
  interface Assertion&#60;T = any&#62; extends CustomMatchers&#60;T&#62; {}
  interface AsymmetricMatchersContaining extends CustomMatchers {}
}
```


```typescript
// droidCommunicator.test.ts
import './customMatchers'; // Import the custom matchers
import { expect, test } from 'vitest';

test('should use custom matcher', () =&#62; {
  expect('D123').toBeDroidId();
  expect('C3PO').not.toBeDroidId();
});
```


**Explanation:**

- `toHaveBeenCalledTimes(number)`: Verifies that a mock function has been called a specific number of times, allowing you to catch unexpected behavior.
- `toHaveBeenCalledWith(...args)`: Verifies that a mock function has been called with specific arguments.
- `toHaveBeenLastCalledWith(...args)`: Verifies that the last call to a mock function was with specific arguments.
- Custom matchers: Allow you to define your own assertion logic for more complex scenarios.

# Best Practices and Conclusion: Crafting Maintainable and Reliable Tests


## Keep Mocks Focused: Mock Only What You Must


One of the most common pitfalls in testing is over-mocking. It's tempting to mock everything, but this can lead to tests that are disconnected from the actual behavior of your code. Remember, the goal of mocking is to isolate the unit under test, not to replace every dependency.


Only mock external dependencies or parts of your code that are difficult to test directly. For example, mock API calls, database interactions, or complex third-party libraries. But avoid mocking the core logic of the unit you're testing.


Ask yourself, "What am I trying to test?" If the answer involves the interaction with an external system or a complex dependency, then mocking is appropriate. Otherwise, try to test the code as it is.


## Maintain Test Readability: Clarity is Key


Tests should be as readable as possible. This makes them easier to understand, maintain, and debug. Here are a few tips:

- **Use descriptive mock names:** Instead of `mockFn`, use names like `mockApiService` or `mockDatabaseSave`.
- **Add comments:** Explain why you're mocking a particular dependency or what a specific assertion is checking.
- **Keep tests concise:** Break down complex tests into smaller, more focused ones.
- **Follow the Arrange-Act-Assert pattern:** This makes your tests easier to follow.

## Summary: Empowering Your Testing Skills


In this guide, we've explored advanced mocking techniques in Vitest, going beyond the basics to tackle complex scenarios. We've covered:

- The purpose and benefits of mocking.
- Core mocking techniques like `vi.spyOn()`, `vi.fn()`, and manual mocks.
- Advanced mocking scenarios, including asynchronous functions, external APIs, browser APIs, and classes.
- Verification and assertion techniques to ensure your mocks are used correctly.
- Fixtures to keep your tests DRY.

By mastering these techniques, you can write more robust, reliable, and maintainable tests. Remember to mock only what's necessary, keep your tests readable, and always verify your mocks with appropriate assertions.


## Further Resources:

- Vitest Documentation: [https://vitest.dev/](https://www.google.com/url?sa=E&#38;q=https%3A%2F%2Fvitest.dev%2F)
- Mock Service Worker (MSW): [https://mswjs.io/](https://www.google.com/url?sa=E&#38;q=https%3A%2F%2Fmswjs.io%2F)
- JSDOM: [https://github.com/jsdom/jsdom](https://www.google.com/url?sa=E&#38;q=https%3A%2F%2Fgithub.com%2Fjsdom%2Fjsdom)

I encourage you to apply these techniques in your own projects and continue to explore the power of Vitest. Happy testing!


# Vitest Mocking Cheatsheet


## Core Mocks

- `vi.spyOn(obj, method)`: Spy on a method, keep original.
- `vi.fn(impl?)`: Create a mock function.
- `vi.mock(path, factory?)`: Mock a module.
- `__mocks__/module.ts`: Manual mock.

## Mock Behavior

- `mockFn.mockImplementationOnce(fn)`: Set mock function behavior once.
- `mockFn.mockReturnValueOnce(val)`: Set return value once.
- `mockFn.mockResolvedValueOnce(val)`: Set resolved promise value once.
- `mockFn.mockRejectValueOnce(err)`: Set rejected promise value.
- `spy.mockRestore()`: Restore original method.

## Assertions

- `expect(mock).toHaveBeenCalledTimes(n)`: Check call count.
- `expect(mock).toHaveBeenCalledWith(...args)`: Check call args.
- `expect(mock).toHaveBeenLastCalledWith(...args)`: Check last call args.

## Browser/API

- `vi.stubGlobal(name, val)`: Mock global object.
- jsdom: Browser env in Node.
- MSW: API mocking (`setupServer`, `rest.get/post`).]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Home Assistant Dashboard Evolution: Streamlined & Stunning in 2025]]></title>
        <description><![CDATA[Visual Clarity & Performance Boost: A Year of Dashboard Evolution with Streamline Card and Bubble Card]]></description>
        <link>https://brunosabot.dev/posts/2025/home-assistant-dashboard-evolution-streamlined-stunning-in-2025/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2025/home-assistant-dashboard-evolution-streamlined-stunning-in-2025/</guid>
        <pubDate>Thu, 13 Feb 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Remember last year when I was all excited about crafting my &#8220;perfect&#8221; Home Assistant dashboard? Man, time flies! It&#8217;s been a year, and like any good smart home setup, mine has evolved quite a bit. My dashboard? Totally different beast now.


If you caught my previous post, &#8220;[Crafting My Perfect Home Assistant Dashboard](https://medium.brunosabot.dev/crafting-my-perfect-home-assistant-dashboard-6bc1fc6d47cd),&#8221; you know I&#8217;m all about a dashboard that&#8217;s not just functional, but also looks slick and gives me the info I need at a glance. Well, get ready, because things have gotten even better!


This time around, I want to show you how I&#8217;ve really streamlined things (pun intended!), ditching some plugins and focusing on making my dashboard even cleaner and more powerful, all thanks to my own little project: [**Streamline Card**](https://github.com/brunosabot/streamline-card).


# Plugin Diet: Slimming Down for Speed and&#160;Style


Last year, I was juggling a bunch of plugins to get the look and feel I wanted. It worked, but it felt a bit&#8230;clunky. Let&#8217;s see what&#8217;s changed in my plugin toolbox:


## Bubble Card: Still My Ride or&#160;Die


![1*fCng9ydG2dU8QeNwMSFkWA.gif](https://cdn-images-1.medium.com/max/1600/1*fCng9ydG2dU8QeNwMSFkWA.gif)


The Bubble Card plugin for Home Assistant


Seriously, [**Bubble Card**](https://github.com/Clooos/Bubble-Card/) is still the MVP of my dashboard. It&#8217;s just so versatile and customizable, it can handle anything I throw at it. If you&#8217;re building a custom dashboard, Bubble Card is still my number one pick, hands down. And honestly, after a year of even more Home Assistant updates and dashboard tinkering, I&#8217;m even _more_ convinced!


What makes Bubble Card so incredible? Let&#8217;s break it down:

- **Unmatched Entity Support:** Bubble Card isn&#8217;t just limited to a few basic entity types&#8202;&#8212;&#8202;it handles **a massive range of Home Assistant entities**. Whether you want to display simple sensor readings, control complex media players, manage climate settings, or visualize data from custom integrations, Bubble Card has you covered. It truly feels like it can display _any_ entity you throw at it, making it the central hub for visualizing your entire smart home.
- **Style it Your Way (with JavaScript Power!):** Customization is king with Bubble Card. You have granular control over every visual aspect, from fonts and colors to spacing and animations. But what truly elevates the styling is **JavaScript integration**. You can use JavaScript directly within your card style configuration to dynamically adjust styles based on entity states, variables, or even external conditions. Want a button to change color based on the temperature? JavaScript styling makes it possible to create truly adaptive and visually intelligent cards. This power to combine CSS-like styling with JavaScript logic is what lets you perfectly tailor your cards to your exact aesthetic and functional needs.
- **The Genius of Sub-Buttons:** Bubble Card truly shines with its incredibly useful sub-button system. Bubble Card&#8217;s sub-buttons are enriching your main card with extra information. Instead of cluttering a single card, use sub-buttons to neatly layer in details and controls. Think of a weather card: the main bubble shows temperature, and sub-buttons add rain, wind, and more. This system is _damned useful_ for making cards information-rich and functional, presenting more data cleanly and accessibly. They are perfect for adding context and control without losing visual clarity.

And the best part? It manages to be this powerful _without_ becoming overly complex. The YAML configuration is well-structured and, once you get the hang of it, surprisingly intuitive. Plus, the developer (who, full disclosure, I occasionally help out 😉) is super responsive and constantly adding new features and improvements based on user feedback. It&#8217;s a plugin that&#8217;s constantly evolving and getting better.


Honestly, for anyone serious about creating a personalized and powerful Home Assistant dashboard, Bubble Card isn&#8217;t just a good option&#8202;&#8212;&#8202;it&#8217;s _the_ option. It&#8217;s the foundation upon which I&#8217;ve built my entire dashboard, and it&#8217;s the plugin I recommend to anyone asking how to take their dashboard to the next level.


## **Streamline Card: My All-in-One Template Powerhouse (Goodbye Decluttering &#38; Config-Template!)**


![1*pG-d2zOXXPElzdU7RKpz5w.png](https://cdn-images-1.medium.com/max/1600/1*pG-d2zOXXPElzdU7RKpz5w.png)


The Streamline Card plugin for Home Assistant


Okay, let&#8217;s be honest. While I loved `decluttering-card` and `config-template-card` for what they did, they always felt like&#8230;separate pieces of the puzzle.  I wanted something more integrated, more powerful, and frankly, _faster_.  That&#8217;s the real story behind **Streamline Card**.  It wasn't just about replacing those plugins; it was about building something _better_ that combined their best features and added a whole lot more.


So, what makes Streamline Card my go-to template engine now? Let me break it down:

- **Template Reusability (Decluttering-card, Reincarnated!):** First and foremost, Streamline Card nails template reusability, just like `decluttering-card` did. Define your card templates once, then reuse them across your entire dashboard, just by changing a few variables. Want to have a consistent look for all your light cards? Battery sensors? Streamline Card makes it a breeze. It takes the core idea of `decluttering-card` and runs with it, making template management even smoother.
- **Dynamic Configuration Supercharged (Config-template-card, Evolved!):** Remember how `config-template-card` let you make cards change based on conditions? Streamline Card takes that concept and cranks it up to eleven! Its templating engine is incredibly flexible, letting you dynamically control _anything_ within your cards based on entity states, variables, time &#8211; you name it. Need a card to look different when it's raining? Want to show different information depending on who's home? Streamline Card&#8217;s got you covered, with a templating system that's more intuitive and powerful than `config-template-card` ever was.
- **Section Layouts &#38; Visibility: Dashboard Organization Built-In:** While I&#8217;m now embracing Home Assistant&#8217;s native layout options for the _overall_ dashboard structure, Streamline Card still packs a punch when it comes to internal card organization. It fully supports **section layouts and visibility conditions**. This means you can create complex card structures with different sections that appear or disappear based on specific criteria. Want to hide advanced controls unless you need them? Need to create collapsible sections within a card to save space? Streamline Card lets you build cards that are not only visually appealing but also intelligently organized.
- **JavaScript Expressions: Unleash the Power of Code:** This is where Streamline Card really shines. Want to go beyond basic YAML templating? Streamline Card lets you embed **raw JavaScript expressions** directly into your card configuration! Just add `_javascript` to any YAML key, and Streamline Card will interpret its value as JavaScript code. This unlocks a _whole new dimension_ of customization. You can perform complex calculations, manipulate data, create dynamic styles, and build truly interactive elements, all with the full power of JavaScript at your fingertips.
- **UI Editor &#38; YAML Flexibility: Your Choice, Your Workflow:** Whether you&#8217;re a YAML purist or prefer the visual approach of the Home Assistant UI, Streamline Card has you covered. It comes with a **built-in UI editor**, making it easy to configure your cards directly in the browser. But don&#8217;t worry, YAML lovers! Streamline Card is still fully configurable via YAML, giving you the flexibility to choose the workflow that suits you best. Mix and match, use the UI for quick tweaks, YAML for complex setups&#8202;&#8212;&#8202;it&#8217;s all up to you.
- **Performance Matters: Faster Updates, Smoother Dashboard:** Finally, and this is a big one for me: **performance**. Streamline Card is designed with performance in mind. It&#8217;s smart about updates, only re-rendering parts of the card when necessary. This &#8220;cherry-picking&#8221; of updates means your dashboard stays snappy and responsive, even with complex cards and dynamic content. Compared to the plugins it replaces, Streamline Card is simply more efficient, leading to a smoother overall dashboard experience.

In short, Streamline Card isn&#8217;t just a replacement for `decluttering-card` and `config-template-card` &#8211; it's a significant _upgrade_. It combines the best aspects of those plugins, adds a ton of new features (like JavaScript expressions and a UI editor), and delivers it all with better performance.  For me, it&#8217;s become the essential engine driving my entire dashboard's dynamic and templated elements.  If you're serious about taking your dashboard customization to the next level, Streamline Card is the plugin you need to check out.


## **Bye-bye card-mod: Bubble Card &#38; Streamline Card Do Styling Better (and&#160;Faster!)**


Yeah, `card-mod` was the king of custom styles for ages, but things changed.  First, **Bubble Card leveled up its own styling game**, offering tons of built-in options for visuals.  Most of my styling? Bubble Card handles it directly now.  Second, for those _extra_ styling needs, **Streamline Card with its JavaScript power** can achieve almost any visual trick, often more efficiently.  Finally, ditching `card-mod` gave my **dashboard a noticeable speed boost**. It just runs smoother and faster without it.  So, `card-mod` is gone, but my dashboard looks just as good (or better!) and runs way faster thanks to Bubble Card and Streamline Card doing the styling work.  Cleaner setup, better performance, same great looks &#8211; a total win!


# **Template Time: Visualizing My Smart Home with Streamline Templates**


Alright, let&#8217;s dive into the real magic of my updated dashboard: **Streamline Templates!** These are the heart and soul of my visual setup, and they&#8217;re all built as **streamline-templates** _**of**_ **Bubble Cards.** Essentially, they are pre-designed Bubble Card configurations within Streamline Card that make visualizing your smart home info clear, quick to grasp, and even a little bit fun. Let me walk you through some of my most essential templates to show you how they transform my dashboard.


## **Battery Template: Your Batteries, Visually&#160;Charged!**


![1*-g4H2zxLrNs9xYgbqESQpg.png](https://cdn-images-1.medium.com/max/1600/1*-g4H2zxLrNs9xYgbqESQpg.png)


Battery template&#160;preview


This template gives you a super clear visual representation of your battery-powered devices right on your dashboard. Instead of just a static icon, you get a dynamic Bubble Card that uses colors and a cool circular progress indicator around the icon to show you the battery level at a glance. It&#8217;s part of the &#8220;streamline-templates&#8221; collection and is ready to be used in your Streamline Card setup!


```yaml
default:
  - name: ''
card:
  type: 'custom:bubble-card'
  card_type: button
  button_type: state
  entity: '[[entity]]'
  name: '[[name]]'
  show_state: true
  columns: 2
  card_layout: large
  styles: |
    .bubble-button-background {
      background-color: ${state &#60; 10 ? 'var(--error-color)' : state &#60; 30 ? 'var(--warning-color)' : ''};
    }
    .bubble-icon-container {
      background-image: conic-gradient(${state &#60; 10 ? 'var(--error-color)' : state &#60; 30 ? 'var(--warning-color)' : 'var(--success-color)'} ${state}%, transparent ${state}%) !important;
      position: relative;
    }
    .bubble-icon-container::before {
      background-color: var(--bubble-button-icon-background-color, var(--bubble-icon-background-color, var(--bubble-secondary-background-color, var(--card-background-color, var(--ha-card-background)))));
      content: " ";
      position: absolute;
      top: 2px;
      left: 2px;
      bottom: 2px;
      right: 2px;
    }
    .bubble-icon {
      color: ${state &#60; 10 ? 'var(--error-color)' : state &#60; 30 ? 'var(--warning-color)' : ''} !important;
      opacity: 0.6 !important;
    }
```


It features:

- **Dynamic CSS Styling:** The `styles:` section uses template magic (`${...}`) to dynamically change card appearance based on battery level (`state`).
- **Color-Coded Levels:** Background and icon colors change to red (critical), yellow (warning), or default (okay) based on battery percentage, using CSS variables for easy customization.
- **Conic Gradient Progress Ring:** A `conic-gradient` on&#160;`.bubble-icon-container` creates a circular progress bar effect around the icon, visually filling up as battery level increases. A&#160;`::before` element cleverly creates a central cutout for the icon.

Essentially, it&#8217;s dynamic CSS styling with color coding and a conic gradient trick to make a visually informative battery level indicator!


## **Cover Template: See Your Shades&#8217; Position at a&#160;Glance!**


![1*9bPhLBPsdIL3ACB4TnvL1A.png](https://cdn-images-1.medium.com/max/1600/1*9bPhLBPsdIL3ACB4TnvL1A.png)


Cover template&#160;preview


This template is all about making your smart covers (like blinds or shutters) super easy to understand and control visually. It&#8217;s a Bubble Card streamline-template that not only lets you open and close your covers, but also gives you a clear visual indication of their current position using a filling effect on the icon. Plus, for Somfy users, it even includes a handy &#8220;My&#8221; position button!


```yaml
default:
  - name: ''
card:
  type: 'custom:bubble-card'
  card_type: cover
  entity: '[[entity]]'
  name: '[[name]]'
  icon_open: mdi:window-shutter-open
  icon_close: mdi:window-shutter
  show_state: true
  rows: 1
  card_layout: large
  sub_button:
    - name: My
      icon: mdi:star
      show_background: true
      tap_action:
        action: call-service
        service: button.press
        target:
          entity_id:
            - '[[my]]'
  styles_javascript: |
    `
    .bubble-cover-card-container {
      gap: 8px;
    }
    .large .bubble-buttons {
      gap: 8px;
      right: 8px;
    }
    .bubble-button {
      background-color: var(--card-background-color, var(--ha-card-background));
      width: 36px;
      height: 36px;
    }
    .bubble-icon {
      opacity: 0.6 !important;
    }
    .large .bubble-sub-button-container {
      margin-right: 0!important;
    }
    .bubble-sub-button {
      width: 36px;
      height: 36px;
    }
    .bubble-state::after {
      content: " - ${states['[[entity]]'].attributes.current_position}%";
      margin-left: 4px;
    }
    .background-on {
      background-color: var(--card-background-color, var(--ha-card-background));
    }
    .bubble-icon-container {
      background-image: linear-gradient(to bottom, var(--state-cover-active-color) ${100-states['[[entity]]'].attributes.current_position}%, transparent ${100-states['[[entity]]'].attributes.current_position}%) !important;
      position: relative;
    }
    `
```


It features:

- **`[[my]]`** **Button (Somfy):** Includes a "My" button (via `[[my]]` variable and `sub_button`) for Somfy covers to trigger their preset "My" position.
- **Linear Gradient Position Fill:** Uses a dynamic `linear-gradient` on&#160;`.bubble-icon-container` (styled with JavaScript) to visually fill the icon based on the cover's `current_position`.
- **Percentage State Display:** Appends the cover&#8217;s `current_position` percentage to the state text using CSS and JavaScript for numerical feedback.

Essentially, it&#8217;s Somfy &#8220;My&#8221; button integration, dynamic icon filling for position, and a percentage readout for a complete cover visualization and control card!


## **Fan Template: Animated Fan&#160;Fun!**


![1*KNegqbo2DI0qyNcP74SNkw.gif](https://cdn-images-1.medium.com/max/1600/1*KNegqbo2DI0qyNcP74SNkw.gif)


Fan template&#160;preview


This template brings your smart fans to life right on your dashboard with a fun, animated icon! It&#8217;s a Bubble Card streamline-template that not only lets you toggle your fan on/off but also visually _shows_ the fan spinning at different speeds thanks to a cool rotation animation. Plus, it includes sub-buttons for oscillation and speed control, giving you full fan command in a single, dynamic card.


```yaml
default:
  - name: ''
card:
  type: 'custom:bubble-card'
  card_type: button
  button_type: switch
  entity: '[[entity]]'
  name: '[[name]]'
  icon: mdi:fan
  show_state: true
  columns: 4
  card_layout: large
  sub_button:
    - icon: mdi:arrow-oscillating
      show_icon: true
      tap_action:
        action: call-service
        service: fan.oscillate
        target:
          entity_id: '[[entity]]'
    - icon: mdi:fan-minus
      show_icon: true
      tap_action:
        action: call-service
        service: fan.decrease_speed
        target:
          entity_id: '[[entity]]'
    - icon: mdi:fan-plus
      show_icon: true
      tap_action:
        action: call-service
        service: fan.increase_speed
        target:
          entity_id: '[[entity]]'
  styles: |
    @keyframes rotate {
      0% {
        transform: rotate(0deg);
      }
      100% {
        transform: rotate(360deg);
      }
    }
    .bubble-icon {
      animation: rotate ${50 / hass.states['[[entity]]'].attributes.percentage}s linear infinite;
    }
    .background-on {
      background-color: var(--card-background-color, var(--ha-card-background));
    }
    .bubble-state::after {
      content: " - ${hass.states['[[entity]]'].attributes.percentage}%";
      margin-left: 4px;
    }
```

- **CSS Animation for Spin:** The template uses CSS `@keyframes rotate` to make the fan icon spin.
- **JavaScript Speed Control:** The key is in&#160;`.bubble-icon` styles: `animation: rotate ${50 / hass.states['[[entity]]'].attributes.percentage}s linear infinite;` This line uses JavaScript to dynamically set the animation speed based on the fan's `percentage` attribute. _Higher speed = faster spin!_
- **Sub-buttons for Control:** Sub-buttons are included for oscillation and speed control (decrease/increase).
- **State Shows Percentage:** The state display is tweaked to show the fan speed percentage numerically.

Essentially, it&#8217;s CSS animation + JavaScript smarts to visually represent fan speed with a spinning icon, plus handy sub-buttons for control!


## **Player Template: Bass-Pumping Visuals for Your&#160;Music!**


![1*hTxwqfJOC-Wh6wKmYwmA-Q.gif](https://cdn-images-1.medium.com/max/1600/1*hTxwqfJOC-Wh6wKmYwmA-Q.gif)


Player template&#160;preview


It has a cool &#8220;bass-effect&#8221; animation on your media player card! This Bubble Card streamline-template makes your media player thumbnails subtly pulse and &#8220;beat&#8221; when music is playing, adding a dynamic and engaging visual element to your dashboard&#8217;s media controls.


```yaml
default:
  - name: ''
card:
  type: 'custom:bubble-card'
  card_type: media-player
  entity: '[[entity]]'
  name: '[[name]]'
  icon: '[[icon]]'
  columns: 4
  card_layout: large
  hide:
    power_button: true
  styles: |
    .bubble-icon-container {
      animation: ${state === 'playing' ? 'player' : 'none'} 2s infinite;
    }
    @keyframes player {
      from { transform: scale(1); filter: blur(0); }
      5% { transform: scale(1); filter: blur(0); }
      10% { transform: scale(1.1); filter: blur(1px); }
      15% { transform: scale(1); filter: blur(0); }
      45% { transform: scale(1); filter: blur(0); }
      50% { transform: scale(1.1); filter: blur(1px); }
      55% { transform: scale(1); filter: blur(0); }
      65% { transform: scale(1); filter: blur(0); }
      70% { transform: scale(1.1); filter: blur(1px); }
      75% { transform: scale(1); filter: blur(0); }
      to { transform: scale(1); filter: blur(0); }
    }
```


It features:

- **CSS** **`player`** **Animation:** Defines a CSS `@keyframes player` animation that creates a subtle "pulse" effect by scaling the icon slightly larger and adding a bit of blur at intervals.
- **Dynamic Animation Toggle:** The&#160;`.bubble-icon-container` style uses JavaScript template magic: `animation: ${state === 'playing'&#160;? 'player'&#160;: 'none'} 2s infinite;`. This line _dynamically_ applies the `player` animation _only when_ the media player's `state` is `playing`. Otherwise, the animation is set to `none`, keeping the icon static when not playing.
- **Hidden Power Button:** `hide: power_button: true` simply hides the default power button on the Bubble Card media player, focusing the visual attention on the animated thumbnail.

In essence, it&#8217;s all about using a CSS animation and JavaScript-based conditional styling to create a fun, subtle &#8220;bass-beat&#8221; visual effect on your media player card when it&#8217;s active!


## **Thermostat Template: Instant Temperature Insights with Color&#160;Coding!**


![1*hfzY9FtZbbN9Wxl2lHlBiQ.png](https://cdn-images-1.medium.com/max/1600/1*hfzY9FtZbbN9Wxl2lHlBiQ.png)


Thermostat template&#160;preview


This Bubble Card streamline-template provides a clear and immediate understanding of your thermostat&#8217;s operation. At a glance, you can see not only the current temperature but also whether your thermostat is actively heating and if it&#8217;s still working to reach the set target temperature, all thanks to intuitive color cues.


```yaml
default:
  - name: 'Thermostat'
card:
  type: custom:bubble-card
  card_type: climate
  entity: '[[entity]]'
  name: '[[name]]'
  show_state: true
  columns: 4
  card_layout: large
  sub_button:
    - name: HVAC modes menu
      select_attribute: hvac_modes
      show_arrow: false
  styles: |
    .bubble-color-background {
      background-color: ${ hass.states['[[entity]]'].state === 'heat' ? hass.states['[[entity]]'].attributes.temperature &#62; hass.states['[[temperature]]'].state ? "var(--warning-color)" : "transparent" : "transparent" } !important;
      opacity: 1!important;
    }
    .bubble-state::after {
      content: " - ${hass.states['[[entity]]'].attributes.current_temperature} &#176;C";
      margin-left: 4px;
    }
    .bubble-sub-button.background-on {
      background-color: var(--card-background-color, var(--ha-card-background));
    }
    .bubble-icon {
      color: ${ hass.states['[[entity]]'].state === 'off' ? "var(--info-color)" : "" } !important;
    }
```


It features:

- **Color Background for Heating:**&#160;`.bubble-color-background` uses JavaScript to dynamically set the background color to `var(--warning-color)` (e.g., orange/yellow) _only when_ heating and the target temperature is still higher than the current temperature. Otherwise, it's transparent.
- **State Shows Current Temp:**&#160;`.bubble-state::after` adds the `current_temperature` in &#176;C to the state display.
- **&#8220;Off&#8221; State Icon Color:**&#160;`.bubble-icon` changes the icon to `var(--info-color)` when the thermostat is `off`.

Essentially, it&#8217;s dynamic color-coding for heating status, clear temperature display, and a visual cue for the &#8220;off&#8221; state&#8202;&#8212;&#8202;all in one concise template!


## Weather Template: Your Complete Weather Briefing at a&#160;Glance!


![1*kCec5N6m1x-RUerDuWnhug.png](https://cdn-images-1.medium.com/max/1600/1*kCec5N6m1x-RUerDuWnhug.png)


Weather template&#160;preview


This Bubble Card streamline-template is an all-in-one weather powerhouse! It displays current conditions, along with color-coded high/low temperatures, wind speed, and rain probability, all within a single, compact card.


```yaml
default:
  - name: ''
card:
  type: custom:bubble-card
  card_type: button
  button_type: state
  entity: '[[entity]]'
  name: '[[name]]'
  show_state: true
  scrolling_effect: false
  card_layout: large-2-rows
  sub_button:
    - name: Min
      icon: mdi:thermometer-low
      entity: '[[entity]]'
      attribute: forecast[0].templow
      show_background: false
      show_attribute: true
    - name: Wind
      icon: mdi:weather-windy
      entity: '[[entity]]'
      attribute: wind_speed
      show_background: false
      show_attribute: true
      show_icon: true
    - name: Max
      icon: mdi:thermometer-high
      entity: '[[entity]]'
      attribute: forecast[0].temperature
      show_background: false
      show_attribute: true
    - name: Rain
      icon: mdi:weather-pouring
      entity: '[[entity]]'
      show_background: false
      show_attribute: true
      attribute: forecast[0].precipitation
      show_icon: true
  styles: |
      .bubble-icon-container {
        box-sizing: border-box;
        padding: 8px;
      }
      .bubble-name-container {
        margin: 0 0 0 0;
        padding: 0 8px;
      }
      .bubble-entity-picture {
        height: calc(100% - 16px);
        width: calc(100% - 16px);
      }
      .bubble-name {
          border-radius: 2px;
          background-color: ${
            hass.states[entity].attributes.temperature &#60; 10
              ? 'var(--info-color)'
              : hass.states[entity].attributes.temperature &#62; 40
              ? 'var(--error-color)'
              : hass.states[entity].attributes.temperature &#62; 30
              ? 'var(--warning-color)'
              : ''
          };
          height: 20px;
          margin-left: -4px;
          padding: 0 4px;
          width: fit-content;
          z-index: 1;
      }
      .bubble-name::after {
          content: " &#183; ${hass.states[card.getRootNode().host.config.sub_button[0].entity].attributes.temperature}&#176;C";
      }
      .bubble-state {
        height: 20px;
      }
      .rows-2 .bubble-sub-button-container {
        gap: 0 8px!important;
      }
      .bubble-sub-button {
          padding: 0 4px;
      }
      .bubble-sub-button-1 {
          background-color: ${
            hass.states[card.getRootNode().host.config.sub_button[0].entity].attributes.temperature &#60; 10
              ? 'var(--info-color)'
              : hass.states[card.getRootNode().host.config.sub_button[0].entity].attributes.temperature &#62; 40
                ? 'var(--error-color)'
                : hass.states[card.getRootNode().host.config.sub_button[0].entity].attributes.temperature &#62; 30
                  ? 'var(--warning-color)'
                  : ''
          };
      }
      .bubble-sub-button-1::before {
        content: "&#176;C";
      }
      .bubble-sub-button-2 {
          background-color: ${
            hass.states[card.getRootNode().host.config.sub_button[0].entity].attributes.wind_speed &#62; 50
              ? 'var(--error-color)'
              : hass.states[card.getRootNode().host.config.sub_button[0].entity].attributes.wind_speed &#62; 20
                ? 'var(--warning-color)'
                : ''
          };
      }
      .bubble-sub-button-2::before {
        content: "km/h";
        margin-left: 2px;
      }
      .bubble-sub-button-3 {
          background-color: ${
            hass.states[card.getRootNode().host.config.sub_button[2].entity].attributes.forecast[0].temperature &#60; 10
              ? 'var(--info-color)'
              : hass.states[card.getRootNode().host.config.sub_button[2].entity].attributes.forecast[0].temperature &#62; 40
                ? 'var(--error-color)'
                : hass.states[card.getRootNode().host.config.sub_button[2].entity].attributes.forecast[0].temperature &#62; 30
                  ? 'var(--warning-color)'
                  : ''
          };
      }
      .bubble-sub-button-3::before {
        content: "&#176;C";
      }
      .bubble-sub-button-4 {
          background-color: ${
            hass.states[card.getRootNode().host.config.sub_button[2].entity].attributes.forecast[0].precipitation &#62; 0
              ? 'var(--info-color)'
              : ''
          };
      }
      .bubble-sub-button-4::before {
        content:" mm";
        margin-left: 2px;
      }
```


It features:

- **All-in-one Weather Card:** Combines current conditions, high/low temps, wind, and rain in one card using sub-buttons.
- **Color-Coded Temperatures:** Card name and Min/Max sub-buttons use dynamic background colors to indicate temperature ranges (cold, normal, warm, hot).
- **Wind &#38; Rain Color Highlights:** Wind and Rain sub-buttons use background colors to emphasize windy and rainy conditions.
- **Sub-buttons for Forecast Details:** Sub-buttons display Min/Max temps, Wind speed, and Rain amount directly on the card.
- **Units in CSS:** CSS&#160;`::before` and&#160;`::after` pseudo-elements add units (&#176;C, km/h, mm) to displayed values in the card name and sub-buttons.

Essentially, this template packs a wealth of color-coded weather information into a single Bubble Card using clever dynamic styling and sub-button organization!


# Conclusion: Level Up Your Dashboard &#38; Stay&#160;Tuned!


So there you have it&#8202;&#8212;&#8202;my Home Assistant dashboard, one year later, now leaner, faster, and even more visually informative thanks to the power of Bubble Card and, especially, Streamline Card! I&#8217;ve truly streamlined my setup, and I&#8217;m loving the results.


If you&#8217;re feeling inspired to revamp your own dashboard, I highly encourage you to dive into **Streamline Card** and its templates. You can find the full, ever-growing template library (with even _more_ goodies than showcased here!) over on [my portfolio&#8217;s page for streamline-card](https://brunosabot.dev/streamline-cards/). And if you&#8217;re curious about the plugin itself or want to contribute, you can find the code and documentation on the [Streamline Card GitHub repository](https://github.com/brunosabot/streamline-card).


If you found this update helpful or inspiring, **give this article a clap!** (or bookmark it for later dashboard tinkering!). And who knows what the _next_ year will bring for my dashboard? New sensors? New automations? Even _more_ Streamline Card templates? You&#8217;ll just have to **wait until next year&#8217;s update to find out!** 😉]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Replacing Let’s Encrypt Email Notifications with Home Assistant]]></title>
        <description><![CDATA[A simple automation for secure and reliable certificate monitoring.]]></description>
        <link>https://brunosabot.dev/posts/2025/replacing-let-s-encrypt-email-notifications-with-home-assistant/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2025/replacing-let-s-encrypt-email-notifications-with-home-assistant/</guid>
        <pubDate>Tue, 04 Feb 2025 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[So, [Let&#8217;s Encrypt](https://letsencrypt.org/), the awesome folks who give us free SSL certificates, have stopped sending email notifications about expiring certificates. Why? Well, turns out managing a massive email list is a security risk (think hackers!) and sending out all those emails costs a pretty penny (and free services gotta stay free, right?).


This leaves us in a bit of a pickle. We _need_ to know when our certificates are about to expire, or our sites will go down! Luckily, our trusty friend [Home Assistant](https://www.home-assistant.io/) can easily fill this gap. It&#8217;s super simple to set up and gives you full control over how you&#8217;re notified. Let&#8217;s dive in!


# Step 1: The Certificate Expiry Integration


First things first, we need to get Home Assistant &#8220;aware&#8221; of our certificates. We do this using the &#8220;[Certificate Expiry](https://www.home-assistant.io/integrations/cert_expiry/)&#8221; integration. Head over to your Home Assistant settings, find the &#8220;Integrations&#8221; section, and add it. You&#8217;ll be asked for some info:

- **Host:** This is the URL of the site using the certificate. For example, `home-assistant.io`.
- **Port:** This is the port the site uses. 99% of the time, you can just leave this at the default `443` (which is the standard port for HTTPS).

Repeat these steps for each certificate you want to monitor. Home Assistant will then create a sensor for each one. These sensors will hold the expiry date, which is exactly what we need!


# Step 2: Automating the Notifications


Now for the magic! We&#8217;ll create an automation in Home Assistant that checks these expiry dates and sends us a notification. Since we&#8217;re using Jinja templating for dynamic messages, we&#8217;ll work directly in YAML mode.

- **Navigate to Automations:** In your Home Assistant frontend, go to _Settings_ -&#62; _Automations &#38; Scenes_.
- **Create New Automation:** Click the &#8220;+ Create Automation&#8221; button.
- **Choose &#8220;Edit in YAML&#8221;:** From the three-dot menu, select &#8220;Edit in YAML&#8221;.
- **Paste and Modify the YAML:** Replace _all_ the generated YAML with the following template:

```plain text
alias: Certificate Expiry
description: ""
triggers:
  - trigger: time
    at:
      entity_id: sensor.home_assistant_io_certificate_expiry
      offset: "-864000"
  - trigger: time
    at:
      entity_id: sensor.brunosabot_dev_certificate_expiry
      offset: "-864000"
conditions: []
actions:
  - data:
      message: &#62;-
        {{ states[trigger.entity_id.split(".")[0]][
        trigger.entity_id.split(".")[1]].name }}
      title: Certificate expiration in 10 days
    action: notify.mobile_app_your_device
mode: single
```


Let&#8217;s break this down, shall we?

- **`alias: Certificate Expiry`**: This is just the name of your automation. Make it something descriptive!
- **`description`**&#160;: This helps you understand what this automation is for. You can fill it with anything you like.
- **`triggers:`**: This section defines _when_ the automation should run. We're using a `time` trigger, which means it'll run at a specific time.
- **`triggers.at.entity_id:`**: This is the sensor we're checking. For example, `sensor.home_assistant_io_certificate_expiry`. You'll have one of these lines for _each_ certificate you're monitoring.
- **`triggers.at.offset: "-864000"`**: This is the clever bit! `864000` represents 10 days in seconds. So, this trigger will fire 10 days _before_ the certificate expires. You can adjust this to whatever timeframe you like. Want a warning a month before? Change it to `2592000` (30 days in seconds).
- **`conditions: []`**: We're keeping this empty for now. You could add conditions here if you wanted the notification to only fire under certain circumstances (e.g., only on weekdays).
- **`actions:`**: This is what happens when the trigger fires.
- **`actions.data:`**: This is the message that gets sent in the notification.
- **`actions.data.message:`**: This is the content of the notification. The magic here is the Jinja template: `{{ states[trigger.entity_id.split(".")[0]][trigger.entity_id.split(".")[1]].name }}`. This dynamically pulls the friendly name of the certificate from the sensor. No hardcoding!
- **`actions.data.title:`**: This is the title of the notification. We're setting it to "Certificate expiration in 10 days," but you can customize it.
- **`actions.action: notify.mobile_app_your_device`**: This is where the notification goes. Replace `mobile_app_your_device` with the name of your mobile app notification service in Home Assistant. This will send a notification to your phone.
- **`mode: single`**: This ensures the automation only runs once, even if the trigger conditions are met multiple times.

# Step 3: Save and&#160;Test!


Save your automation and give it a test run! You can manually trigger it from the Home Assistant interface. You should get a notification on your phone.


And that&#8217;s it! You&#8217;ve now got Home Assistant handling your certificate expiry notifications!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Mastering Complex Templates in Home Assistant: A Comprehensive Guide]]></title>
        <description><![CDATA[Unlock the Full Potential of Automation with Custom Templates]]></description>
        <link>https://brunosabot.dev/posts/2024/mastering-complex-templates-in-home-assistant-a-comprehensive-guide/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2024/mastering-complex-templates-in-home-assistant-a-comprehensive-guide/</guid>
        <pubDate>Tue, 06 Aug 2024 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Introduction


## **Why Create Custom Templates?**


[Home Assistant](https://www.home-assistant.io/) is already pretty smart. It can turn on lights, adjust your thermostat, and even play your favorite music. But imagine if you could teach it to do even more, exactly the way _you_ want it to. That's where templates come in.


Templates are like secret codes that tell Home Assistant what to do in different situations. They can make your home super efficient, comfortable, and personalized.


In this guide, we'll walk you through creating your own powerful templates. We'll break down the process step-by-step, so even if you're new to Home Assistant, you'll be creating complex automations in no time.


**Ready to supercharge your smart home? Let's dive in!**


# Building a Better Weather Sensor


## Why Reinvent the Wheel?


Home Assistant used to have a built-in weather sensor with lots of useful details like the forecast. Unfortunately, this feature was removed in a recent update. But don't worry, we can recreate it using templates!


**A word of caution:** While creating complex templates can be fun and rewarding, remember that simpler templates are often better for system performance. So, let's aim to create a weather sensor that provides essential information without overwhelming your system.


## Our Goal


We're going to build a custom weather sensor that looks and feels similar to the old one. It will include information like:

- Current temperature
- Humidity
- Wind speed
- Weather condition (sunny, cloudy, rainy, etc.)
- A forecast for the next few days
- &#8230;

By the end of this guide, you'll have a powerful weather sensor that provides all the information you need.


Let's start by breaking down the components of our template. In the next chapter, we'll dive into the `trigger` key.


**Are you ready to get started?**


# Building Your Template


## Deciding When to Refresh Your Weather - The Trigger Section


### What is a Trigger?


A trigger is like a doorbell for your automation. It tells Home Assistant when to start running the template. In our case, we want to update the weather information regularly.


### Two Ways to Schedule Updates


We'll focus on two main ways to trigger the weather update:


### Home Assistant Event

- **Platform**: `homeassistant`
- **Event**: `start` - This option triggers the template when Home Assistant starts. While simple, it might not be ideal for frequent updates.

```yaml
trigger:
  - platform: homeassistant
    event: start
```


### Time Pattern

- **Platform:** `time_pattern`
- **Hours:** `/1` - This option triggers the template every hour. For more frequent updates, you can adjust the hours value (e.g., `/2` for every two hours).

```yaml
trigger:
  - platform: time_pattern
    hours: /1
```


### Beyond These Options


While these two methods are common, [Home Assistant offers many other trigger platforms](https://www.home-assistant.io/docs/automation/trigger#webhook-trigger), including:

- **Event:** Triggered by specific events within Home Assistant.
- **State:** Triggered when an entity changes state (e.g., a door sensor opening).
- **Webhook:** Triggered when an external request is made to a specific webhook.

By understanding different triggers, you can create highly customized and efficient automations.


## What Should Your Home Do? - The Action Section


The `action` section is where the magic happens. It defines what your template should do when triggered. For our weather sensor, we'll fetch weather data and create a sensor with the results.


### Fetching Weather Forecasts


To get weather forecast information, we'll use the powerful `weather.get_forecasts` service. This service allows you to retrieve weather data for multiple locations in a single request, making it efficient and convenient for managing forecasts across various areas.


**Example:**


```yaml
action:
  - service: weather.get_forecasts
    data:
      type: daily  # Adjust to "hourly" for hourly forecasts
      target:
        entity_id: 
          - weather.paris
          - weather.new_york
          - weather.tokyo
      response_variable: daily_forecasts  # Customize the variable name if needed
```


This action will fetch daily weather forecasts for the specified locations in your Home Assistant configuration (replace with your desired locations). Remember, you can easily modify the `entity_id` list to include the weather entities you're interested in.


**Important Note:** The specific weather data points available (e.g., temperature, humidity) may vary depending on the weather integration you're using. Consult your integration's documentation for details.


This revision emphasizes the flexibility of the approach and acknowledges potential variations in data availability based on the weather integration.


## **Leveraging Sensor Data - The Sensor Section**


To create a truly informative and visually appealing weather sensor, we can leverage the power of templates to customize its appearance and behavior.


```yaml
sensor:
  - name: Weather Daily Paris
    state: "{{ states('weather.paris') }}"
    picture: &#62;
      {% if states['weather.paris'].state == 'clear-day' %}/local/images/weather/sunny.png
      {% elif states['weather.paris'].state == 'clear-night' %}/local/images/weather/clear_night.png
      {% elif states['weather.paris'].state == 'cloudy' %}/local/images/weather/cloudy.png
      {% elif states['weather.paris'].state == 'overcast' %}/local/images/weather/cloudy.png
      {% elif states['weather.paris'].state == 'fog' %}/local/images/weather/fog.png
      {% elif states['weather.paris'].state == 'hail' %}/local/images/weather/mixed_rain.png
      {% elif states['weather.paris'].state == 'lightning' %}/local/images/weather/lightning.png
      {% elif states['weather.paris'].state == 'lightning-rainy' %}/local/images/weather/storm.png
      {% elif states['weather.paris'].state == 'partly-cloudy-day' %}/local/images/weather/mostly_cloudy.png
      {% elif states['weather.paris'].state == 'partly-cloudy-night' %}/local/images/weather/mostly_cloudy_night.png
      {% elif states['weather.paris'].state == 'partlycloudy' %}/local/images/weather/mostly_cloudy.png
      {% elif states['weather.paris'].state == 'pouring' %}/local/images/weather/heavy_rain.png
      {% elif states['weather.paris'].state == 'rain' %}/local/images/weather/rainy.png
      {% elif states['weather.paris'].state == 'rainy' %}/local/images/weather/rainy.png
      {% elif states['weather.paris'].state == 'sleet' %}/local/images/weather/mixed_rain.png
      {% elif states['weather.paris'].state == 'snow' %}/local/images/weather/snowy.png
      {% elif states['weather.paris'].state == 'snowy' %}/local/images/weather/snowy.png
      {% elif states['weather.paris'].state == 'snowy-rainy' %}/local/images/weather/mixed_rain.png
      {% elif states['weather.paris'].state == 'sunny' %}/local/images/weather/sunny.png
      {% elif states['weather.paris'].state == 'wind' %}/local/images/weather/windy.png
      {% elif states['weather.paris'].state == 'windy' %}/local/images/weather/windy.png
      {% elif states['weather.paris'].state == 'windy-variant' %}/local/images/weather/windy.png
      {% elif states['weather.paris'].state == 'humidity' %}/local/images/weather/humidity.png
      {% elif states['weather.paris'].state == 'pressure' %}/local/images/weather/pressure.png
      {% endif %}
    attributes:
      temperature: "{{ state_attr('weather.paris', 'temperature') }}"
      dew_point: "{{ state_attr('weather.paris', 'dew_point') }}"
      temperature_unit: "{{ state_attr('weather.paris', 'temperature_unit') }}"
      humidity: "{{ state_attr('weather.paris', 'humidity') }}"
      cloud_coverage: "{{ state_attr('weather.paris', 'cloud_coverage') }}"
      pressure: "{{ state_attr('weather.paris', 'pressure') }}"
      pressure_unit: "{{ state_attr('weather.paris', 'pressure_unit') }}"
      wind_bearing: "{{ state_attr('weather.paris', 'wind_bearing') }}"
      wind_speed: "{{ state_attr('weather.paris', 'wind_speed') }}"
      wind_speed_unit: "{{ state_attr('weather.paris', 'wind_speed_unit') }}"
      visibility_unit: "{{ state_attr('weather.paris', 'visibility_unit') }}"
      precipitation_unit: "{{ state_attr('weather.paris', 'precipitation_unit') }}"
      forecast: "{{ daily_forecasts['weather.paris'].forecast }}"
```


Let's break down the template provided:

- **Name:** This defines the name of the sensor, which will be displayed in the Home Assistant interface. In this case, it's "Weather Daily Paris."
- **State:** The `state` property determines the main value displayed by the sensor. Here, it's set to the state of the `weather.paris` entity. This is used for basic information display: we just want to keep the content of the actual weather sensor
- **Picture:** This dynamically assigns an image based on the current weather state using conditional statements. You can customize the image paths (`/local/images/weather/...`) and potentially the weather conditions that trigger specific images based on your preferences and weather integration.
- **Attributes:** This section defines additional details about the weather conditions. It includes temperature, dew point, humidity, wind speed, and other relevant data points. These attributes can be used to display information in the sensor's card or used in other automations. Here again, it&#8217;s a basic copy of the original weather sensor attributes
- **Forecast:** This attribute leverages the data fetched using the `weather.get_forecasts` service explained in the previous chapter. It stores the forecast information for Paris, which can be an array containing details for multiple days depending on your weather integration.

By combining these elements, you can create a comprehensive weather sensor in Home Assistant. Remember to replace the placeholder image paths with your actual image files, and easily replicate this template for different locations to build a robust weather monitoring system.


# Bringing It All Together


Now that we've covered the essential components, let's assemble a complete template for our weather sensor.


## The full template


```yaml
trigger:
  - platform: time_pattern
    hours: /1
  - platform: homeassistant
    event: start
action:
  - service: weather.get_forecasts
    data:
      type: daily
    target:
      entity_id: 
        - weather.paris
        - weather.new_york
        - weather.tokyo
    response_variable: daily_forecasts
sensor:
  - name: Weather Daily Paris
    state: "{{ states('weather.paris') }}"
    picture: &#62;
      {% if states['weather.paris'].state == 'clear-day' %}/local/images/weather/sunny.png
      {% elif states['weather.paris'].state == 'clear-night' %}/local/images/weather/clear_night.png
      {% elif states['weather.paris'].state == 'cloudy' %}/local/images/weather/cloudy.png
      {% elif states['weather.paris'].state == 'overcast' %}/local/images/weather/cloudy.png
      {% elif states['weather.paris'].state == 'fog' %}/local/images/weather/fog.png
      {% elif states['weather.paris'].state == 'hail' %}/local/images/weather/mixed_rain.png
      {% elif states['weather.paris'].state == 'lightning' %}/local/images/weather/lightning.png
      {% elif states['weather.paris'].state == 'lightning-rainy' %}/local/images/weather/storm.png
      {% elif states['weather.paris'].state == 'partly-cloudy-day' %}/local/images/weather/mostly_cloudy.png
      {% elif states['weather.paris'].state == 'partly-cloudy-night' %}/local/images/weather/mostly_cloudy_night.png
      {% elif states['weather.paris'].state == 'partlycloudy' %}/local/images/weather/mostly_cloudy.png
      {% elif states['weather.paris'].state == 'pouring' %}/local/images/weather/heavy_rain.png
      {% elif states['weather.paris'].state == 'rain' %}/local/images/weather/rainy.png
      {% elif states['weather.paris'].state == 'rainy' %}/local/images/weather/rainy.png
      {% elif states['weather.paris'].state == 'sleet' %}/local/images/weather/mixed_rain.png
      {% elif states['weather.paris'].state == 'snow' %}/local/images/weather/snowy.png
      {% elif states['weather.paris'].state == 'snowy' %}/local/images/weather/snowy.png
      {% elif states['weather.paris'].state == 'snowy-rainy' %}/local/images/weather/mixed_rain.png
      {% elif states['weather.paris'].state == 'sunny' %}/local/images/weather/sunny.png
      {% elif states['weather.paris'].state == 'wind' %}/local/images/weather/windy.png
      {% elif states['weather.paris'].state == 'windy' %}/local/images/weather/windy.png
      {% elif states['weather.paris'].state == 'windy-variant' %}/local/images/weather/windy.png
      {% elif states['weather.paris'].state == 'humidity' %}/local/images/weather/humidity.png
      {% elif states['weather.paris'].state == 'pressure' %}/local/images/weather/pressure.png
      {% endif %}
    attributes:
      temperature: "{{ state_attr('weather.paris', 'temperature') }}"
      dew_point: "{{ state_attr('weather.paris', 'dew_point') }}"
      temperature_unit: "{{ state_attr('weather.paris', 'temperature_unit') }}"
      humidity: "{{ state_attr('weather.paris', 'humidity') }}"
      cloud_coverage: "{{ state_attr('weather.paris', 'cloud_coverage') }}"
      pressure: "{{ state_attr('weather.paris', 'pressure') }}"
      pressure_unit: "{{ state_attr('weather.paris', 'pressure_unit') }}"
      wind_bearing: "{{ state_attr('weather.paris', 'wind_bearing') }}"
      wind_speed: "{{ state_attr('weather.paris', 'wind_speed') }}"
      wind_speed_unit: "{{ state_attr('weather.paris', 'wind_speed_unit') }}"
      visibility_unit: "{{ state_attr('weather.paris', 'visibility_unit') }}"
      precipitation_unit: "{{ state_attr('weather.paris', 'precipitation_unit') }}"
      forecast: "{{ daily_forecasts['weather.paris'].forecast }}"
  - name: Weather Daily New York
    state: "{{ states('weather.new_york') }}"
		# Repeat the same picture and attribute, with a differnt the entity_id
  - name: Weather Daily Tokyo
    state: "{{ states('weather.tokyo') }}"
		# Repeat the same picture and attribute, with a differnt the entity_id
```


To use it, you need to add this as an array item of the `template:` key, inside your `configuration.yaml` file.


# Conclusion and Further Exploration


## Wrapping Up


You've now mastered the fundamentals of creating complex templates in Home Assistant, with a focus on building robust weather sensors. By combining triggers, actions, and custom sensors, you can create highly informative and personalized automations.


Remember to experiment with different weather integrations, explore additional data points, and refine your templates to match your specific needs.


## Going Beyond the Basics


While we've covered the templates core concepts, there's still plenty to explore:

- **Advanced template logic:** Dive deeper into template language to create complex conditions and calculations.
- **Visualizations:** Use custom cards or dashboards to display weather information effectively.
- **Integrations:** Combine weather data with other systems (e.g., smart home devices, calendars) for advanced automations.

By continuously learning and experimenting, you can unlock the full potential of Home Assistant and create truly impressive smart home solutions.


**Happy automating!**]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Crafting My Perfect Home Assistant Dashboard]]></title>
        <description><![CDATA[Learn how I built a sleek & functional Home Assistant dashboard cards by customizing weather, alarms, & more to create an awesome smart home experience.]]></description>
        <link>https://brunosabot.dev/posts/2024/crafting-my-perfect-home-assistant-dashboard/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2024/crafting-my-perfect-home-assistant-dashboard/</guid>
        <pubDate>Sat, 13 Jul 2024 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[**&#10145;️  DASHBOARD UPDATE!  See My 2025 Version Here!**


My Home Assistant dashboard got a major upgrade this year! Click here to see the new, streamlined setup:  "[Home Assistant Dashboard Evolution: Streamlined &#38; Stunning in 2025](https://brunosabot.dev/posts/2025/home-assistant-dashboard-evolution-streamlined-stunning-in-2025/)"


---


Inspired by the beautiful and functional dashboards I've seen online, I embarked on a journey to personalize my own Home Assistant experience. This post dives into the plugins I used, my customization choices, and a final glimpse of the finished product.


# **Introducing the Plugin Powerhouse**


Several plugins played a pivotal role in building my dream dashboard, each offering unique functionalities that elevated the user experience.


I meticulously handpicked a select few from the vast plugin library, prioritizing consistency and cohesiveness around the versatile [Bubble Card](https://github.com/Clooos/Bubble-Card), to craft this personalized experience.


## Bubble Card


![bubble-card.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/bubble-card.png)


I recently stumbled upon the [Bubble Card](https://github.com/Clooos/Bubble) plugin, and let me tell you, it was a game-changer!


This new plugin quickly became the star of my dashboard makeover. Its cool, rounded design elements totally revamped the whole look and feel, making everything smooth and visually appealing.


To take things to the next level, I also started using the [Bubble](https://github.com/Clooos/Bubble) theme, created by the same awesome creator.


Together, they work like magic, creating a consistent and stunning dashboard that's more than just the sum of its parts.


It's like my dashboard went from info central to an interactive and visually engaging masterpiece!


## Card Mod


Anyone who's ever tinkered with their dashboard to make it look snazzy probably knows about [Card Mod](https://github.com/thomasloven/lovelace-card-mod) &#8211; it's kind of a no-brainer.


This awesome plugin lets you, well, mod your cards by taking styles from other cards. Need to change the background color, for example? Card Mod is your new best friend.


It helps if you know a little CSS, but even without it, you can do some pretty cool stuff and make your dashboard look way less, well, boring.


## Decluttering Card


Card Mod is so cool, I went a little overboard and ended up customizing practically everything! But all those tweaks scattered throughout my config started to feel like a bit of a pain to manage.


That's when I discovered the magic of [Decluttering Card](https://github.com/custom-cards/decluttering-card). This plugin lets me create card templates that I can reuse everywhere, shrinking my configuration file down to a much more manageable size.


Now, I can make a change in one place, and it ripples through my entire dashboard &#8211; a total win!


## Config Template Card


Decluttering Card definitely helps with the templating issue, but it can't handle everything, especially when things get a little more dynamic.


Thankfully, the [Config Template Card](https://github.com/iantrich/config-template-card) swoops in to save the day! This plugin lets you use JavaScript to make pretty much any property of your card dynamic, putting the finishing touches on your template mastery.


It's like the final ingredient in the recipe for perfect dashboard customization.


## Lovelace Layout Card


Now I have the power to create the perfect cards, I just needed the final touch: a stellar layout.


Thankfully, [Lovelace Layout Card](https://github.com/thomasloven/lovelace-layout-card) swoops in to save the day! This plugin allows me to craft a perfectly responsive dashboard that flawlessly adapts to both my desktop and mobile, ensuring a seamless Home Assistant experience no matter the device I'm using.


# **Bringing it to Life: My Customization Journey**


In the following sections, I'll delve into the specifics of how I utilized these plugins to realize my desired dashboard functionalities.


I'll provide a detailed explanation for each, empowering you to replicate or adapt these approaches to craft your own personalized Home Assistant experience.


## Alarm


![alarm.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/alarm.png)


Keeping tabs on my home alarm system's status is crucial. To achieve this, I needed a clear and immediate visual indicator right on my dashboard.


The ideal card would dynamically change its background color based on the alarm's state: green for disabled, orange for armed, and red for triggered. Luckily, the style customization of Bubble Card allowed me to create this functionality perfectly.


Now, with just a glance, I can see the alarm's status and take appropriate action if needed.


Here is the decluttering-card template:


```yaml
default:
  - name: ''
  - columns: 2
card:
  type: custom:bubble-card
  card_type: button
  button_type: state
  entity: '[[entity]]'
  name: '[[name]]'
  show_state: true
  icon: mdi:alarm-light
  columns: '[[columns]]'
  card_layout: large
  styles: |
    .bubble-button-card-container {
      background-color: ${state === 'disarmed' ? 'var(--success-color)' : state === 'triggered' ? 'var(--error-color)' : 'var(--warning-color)'};
    }
    .bubble-icon {
      color: ${state === 'disarmed' ? 'var(--success-color)' : state === 'triggered' ? 'var(--error-color)' : 'var(--warning-color)'} !important;
      opacity: 0.6 !important;
    }
```


## AQI


![aqi.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/aqi.png)


Monitoring air quality is a top priority for me, as I'm sensitive to pollution.


Having this data readily available on my dashboard helps me maintain my health, not only through automations with my air purifier but also by allowing me to check the current pollution level at a glance.


I envisioned a card that would display the Air Quality Index (AQI) using a color-coded system: green for good air quality (AQI below 50), orange for moderate air quality (AQI below 100), and red for poor air quality (AQI above 100).


This way, I could easily understand the air quality and take appropriate actions, if necessary.


Here is the decluttering-card template:


```yaml
default:
  - name: ''
card:
  type: 'custom:bubble-card'
  card_type: button
  button_type: state
  entity: '[[entity]]'
  name: '[[name]]'
  icon: mdi:leaf
  show_state: true
  columns: 2
  card_layout: large
  styles: |
    .bubble-button-card-container {
      background-color: ${state &#60; 50 ? 'var(--success-color)' : state &#60; 100 ? 'var(--warning-color)' : 'var(--error-color)'};
    }
    .bubble-icon {
      color: ${state &#60; 50 ? 'var(--success-color)' : state &#60; 100 ? 'var(--warning-color)' : 'var(--error-color)'} !important;
      opacity: 0.6 !important;
    }
```


## Covers


![cover.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/cover.png)


My cover cards demanded a presentation that was both information-rich and visually clear. To achieve this, I implemented a three-pronged approach:

- **Condensed Icon Spacing &#38; Favorite Position Integration:** I tightened the spacing between the cover icons, fostering a sense of cohesion for the linked actions they represent. Additionally, I incorporated a sub-button displaying the favorite position (set at 15% in my case) directly within the card itself.
- **Seamless Opening Percentage Display:** The current opening percentage is crucial information, and I eliminated the need for a separate entity card by integrating it directly into the cover card.
- **Color-Coded Status Recognition:** Finally, I implemented a color-coding system to provide instant status recognition. Blue signifies a fully open cover, while purple highlights the preferred 15% position. This visual language allows for quick comprehension and informed decision-making when interacting with my covers.

This combined approach effectively streamlined the cover card format, enhancing both information accessibility and user experience.


Here is the decluttering-card template:


```yaml
default:
  - name: ''
card:
  type: 'custom:bubble-card'
  card_type: cover
  entity: '[[entity]]'
  name: '[[name]]'
  icon_open: mdi:window-shutter-open
  icon_close: mdi:window-shutter
  show_state: true
  columns: 2
  card_layout: large
  sub_button:
    - name: My
      icon: mdi:star
      show_background: false
      tap_action:
        action: call-service
        service: button.press
        target:
          entity_id:
            - '[[my]]'
  card_mod:
    style: |
      .bubble-cover-card-container {
        background-color:
          {% if state_attr(config.entity, 'current_position') == 15 %}
            var(--state-cover-active-color)
          {% elif state_attr(config.entity, 'current_position') &#62; 0 %}
            var(--accent-color)
          {% else %}
            var(--background-color-2, var(--secondary-background-color))
          {% endif %}
        !important;
      }
      .large .bubble-buttons {
        gap: 8px;
      }
      .bubble-button {
        background: transparent;
        width: 40px;
        height: 40px;
      }
      .bubble-icon {
        justify-content: center;
        align-items: center;
      }
      .large .bubble-sub-button-container {
        margin-right: 8px;
      }
      .bubble-sub-button {
        background: transparent;
        width: 40px;
        height: 40px;
      }
      .bubble-state::after {
        content: " - {{ state_attr(config.entity, 'current_position') }}%";
        margin-left: 4px;
      }
      .bubble-icon-container {
        border-color: transparent;
        background-color: transparent;
      }
      .bubble-icon-container::after {
        content: "";
        background-color:
          {% if state_attr(config.entity, 'current_position') == 15 %}
            var(--state-cover-active-color)
          {% elif state_attr(config.entity, 'current_position') &#62; 0 %}
            var(--accent-color)
          {% else %}
            var(--background-color-2, var(--secondary-background-color))
          {% endif %}
        !important;
        height: 25px;
        width: 25px;
        position: absolute;
        left: 50px;
        top: 25px;
        -webkit-mask-image: radial-gradient(circle at top right, transparent 0, transparent 25px, black 25.5px);
      }
```


## Separator


![separator.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/separator.png)


To keep my dashboard organized, I grouped things into sections.


Bubble Card's separator mode was perfect for adding section titles, but I wanted to give them a little makeover.


So, I played around with some CSS to make the font lighter and bigger, and tweaked the line color so it stands out even when the cards aren't in a popup.


Now the section titles are easy to read and add a nice touch to the whole thing!


Here is the decluttering-card template:


```yaml
default:
  - icon: ''
  - name: ''
card:
  type: custom:bubble-card
  card_type: separator
  name: '[[name]]'
  icon: '[[icon]]'
  card_layout: large
  styles: |
    .bubble-name {
      font-weight: 100;
      font-size: 20px;
    }
    .bubble-line {
      background-color: var(--background-color-2, var(--secondary-background-color));
      opacity: 0.2;
    }
```


## Thermostat


![thermostat.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/thermostat.png)


Craving more from my smart thermostat display, I used custom-template-card to dynamically change the card's icon based on heating state.


Color cues joined the party: blue for off, green for achieved comfort, and orange for heating to reach temperature. I even incorporated the target temperature in this customized switch card.


Now, it's a one-stop shop for efficient and comfortable climate control!


```yaml
default:
  - name: ''
  - offset: 1
card:
  type: custom:config-template-card
  variables:
    MODE: "states['[[entity]]'].state"
  entities:
    - "[[entity]]"
  card: 
    type: custom:bubble-card
    card_type: button
    button_type: switch
    entity: '[[entity]]'
    name: '[[name]]'
    icon: "${MODE === 'heat' ? 'mdi:thermometer': 'mdi:thermometer-off'}"
    show_state: true
    columns: 2
    card_layout: large
    card_mod:
      style: |
        .bubble-button-card-container {
          background-color: {% if is_state(config.entity, 'heat') %}{% if state_attr(config.entity, 'temperature') &#62; state_attr(config.entity, 'current_temperature') %}var(--warning-color){% else %}var(--success-color){% endif %}{% else %}var(--info-color){% endif %} !important;
        }
        .bubble-icon {
          color: {% if is_state(config.entity, 'heat') %}{% if state_attr(config.entity, 'temperature') &#62; state_attr(config.entity, 'current_temperature') %}var(--warning-color){% else %}var(--success-color){% endif %}{% else %}var(--info-color){% endif %} !important;
          opacity: 0.6 !important;
        }
        .bubble-state::after {
          content: " - {{ state_attr(config.entity, 'temperature') }} &#176;C";
          margin-left: 4px;
        }
```


## Weather


![weather.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/weather.png)


While Bubble Card forms the foundation, the true magic lies in the customization of my weather display. It leverages the power of sub-buttons to showcase a range of essential information: current conditions, daily highs and lows, wind speed, and the anticipated rain forecast.


But the key is the intuitive color-coding system I implemented:

- **Temperatures** utilize a blue-to-red gradient, providing a quick glimpse of the expected highs and lows.
- **Wind speed** follows a similar approach, transitioning from neutral to red as intensity increases.
- **Rain** takes center stage with a prominent blue highlight whenever there's a chance of precipitation.

This visual language allows me to grasp the daily forecast at a glance, without getting bogged down in numbers. It's a testament to the power of visual communication and user experience design in action!


```yaml
default:
  - name: ''
card:
  type: custom:bubble-card
  card_type: button
  button_type: state
  entity: '[[entity]]'
  name: '[[name]]'
  show_state: true
  scrolling_effect: false
  card_layout: large-2-rows
  sub_button:
    - name: Min
      icon: mdi:thermometer-low
      entity: '[[entity]]'
      attribute: forecast[0].templow
      show_background: false
      show_attribute: true
    - name: Wind
      icon: mdi:weather-windy
      entity: '[[entity]]'
      attribute: wind_speed
      show_background: false
      show_attribute: true
      show_icon: true
    - name: Max
      icon: mdi:thermometer-high
      entity: '[[entity]]'
      attribute: forecast[0].temperature
      show_background: false
      show_attribute: true
    - name: Rain
      icon: mdi:weather-pouring
      entity: '[[entity]]'
      show_background: false
      show_attribute: true
      attribute: forecast[0].precipitation
      show_icon: true
  card_mod:
    style: |
      .bubble-icon-container {
        background: transparent;
      }
      .bubble-name {
          border-radius: 20px;
          background-color:
          {% if state_attr(config.entity, 'temperature') &#60; 10 %}
            var(--info-color)
          {% elif state_attr(config.entity, 'temperature') &#62; 40 %}
            var(--error-color)
          {% elif state_attr(config.entity, 'temperature') &#62; 30 %}
            var(--warning-color)
          {% endif %}
          ;
          margin-left: -8px;
          padding: 0 8px;
      }
      .bubble-name::after {
          content: " - {{ state_attr(config.sub_button[0].entity, 'temperature')  }}&#176;C";
          margin-left: 4px;
      }
      .bubble-sub-button-1 {
          background-color:
          {% if state_attr(config.sub_button[0].entity, 'forecast')[0].templow &#60; 10 %}
            var(--info-color)
          {% elif state_attr(config.sub_button[0].entity, 'forecast')[0].templow &#62; 40 %}
            var(--error-color)
          {% elif state_attr(config.sub_button[0].entity, 'forecast')[0].templow &#62; 30 %}
            var(--warning-color)
          {% endif %}
          ;
      }
      .bubble-sub-button-1::before {
        content: "&#176;C";
      }
      .bubble-sub-button-2 {
          background-color:
          {% if state_attr(config.sub_button[0].entity, 'wind_speed') &#62; 20 %}
            var(--warning-color)
          {% elif state_attr(config.sub_button[0].entity, 'wind_speed') &#62; 50 %}
            var(--error-color)
          {% endif %}
          ;
      }
      .bubble-sub-button-2::before {
        content: "km/h";
        margin-left: 2px;
      }
      .bubble-sub-button-3 {
          background-color:
          {% if state_attr(config.sub_button[2].entity, 'forecast')[0].temperature &#60; 10 %}
            var(--info-color)
          {% elif state_attr(config.sub_button[2].entity, 'forecast')[0].temperature &#62; 40 %}
            var(--error-color)
          {% elif state_attr(config.sub_button[2].entity, 'forecast')[0].temperature &#62; 30 %}
            var(--warning-color)
          {% endif %}
          ;
      }
      .bubble-sub-button-3::before {
        content: "&#176;C";
      }
      .bubble-sub-button-4 {
          background-color:
          {% if state_attr(config.sub_button[3].entity, 'forecast')[0].precipitation &#62; 0 %}
            var(--info-color)
          {% endif %}
          ;
      }
      .bubble-sub-button-4::before {
        content:" mm";
        margin-left: 2px;
      }
```


## Weather forecast


![weather-forecast.png](https://storage.googleapis.com/brunosabot.dev/img/bubble-ha/weather-forecast.png)


While the current weather is important, I also crave a sneak peek at the next week's forecast.


To achieve this, I leveraged the same color-coded system from my weather card for a visually cohesive experience. Additionally, I cleverly formatted the current day using its attributes, while also extracting the high and low temperatures from the same source.


This way, I get a clear picture of the upcoming week's weather trends right on my dashboard.


```yaml
default:
  - name: ''
  - offset: 1
card:
  type: custom:config-template-card
  variables:
    FORECAST: "states['[[entity]]'].attributes.forecast[[[offset]]].condition"
  entities:
    - "[[entity]]"
  card: 
    type: custom:bubble-card
    card_type: button
    button_type: state
    entity: '[[entity]]'
    name: Forecast
    icon: "${FORECAST === 'partlycloudy' ? 'mdi:weather-partly-cloudy': 'mdi:weather-'+FORECAST}"
    columns: 2
    card_layout: large
    show_state: true
    card_mod:
      style:
        .: |
          .bubble-name {
            color: transparent;
            white-space: nowrap;
          }
          .bubble-name:before {
            color: var(--primary-text-color);
            content: "{{ strptime(state_attr(config.entity, 'forecast')[[[offset]]].datetime, '%Y-%m-%dT%H:%M:%S%z').strftime("%A")  }}";
          }
          .bubble-state {
            color: transparent;
            white-space: nowrap;
          }
          .bubble-state:before {
            color: var(--primary-text-color);
            content: "{{ state_attr(config.entity, 'forecast')[[[offset]]].templow }} / {{ state_attr(config.entity, 'forecast')[[[offset]]].temperature }} {{ state_attr(config.entity, 'temperature_unit') }}";
          }
          .bubble-button-card {
            background-color:
            {% if state_attr(config.entity, 'forecast')[[[offset]]].temperature &#60; 10 %}
              var(--info-color)
            {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature &#62; 40 %}
              var(--error-color)
            {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature &#62; 30 %}
              var(--warning-color)
            {% endif %}
            !important;
          }
          .bubble-icon {
            color: 
            {% if state_attr(config.entity, 'forecast')[[[offset]]].temperature &#60; 10 %}
              var(--info-color)
            {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature &#62; 40 %}
              var(--error-color)
            {% elif state_attr(config.entity, 'forecast')[[[offset]]].temperature &#62; 30 %}
              var(--warning-color)
            {% endif %} !important;
            opacity: 0.6 !important;
          }
```


# Is that it?


While I've highlighted some key customizations, there are additional tweaks throughout my dashboard that refine the presentation further. These refinements build upon the techniques described here. If you're curious to delve deeper, feel free to reach out &#8211; I'm always happy to chat about Home Assistant!


For a complete picture, here's a quick reference of the custom cards I've utilized:

- **Alarm Status:**&#160;`bubble_alarm`&#160;(presented earlier)
- **Air Quality Index:**&#160;`bubble_aqi`&#160;(presented earlier)
- **Cover Cards:**&#160;`bubble_cover`&#160;(presented earlier)
- **Section Separators:**&#160;`bubble_separator`&#160;(presented earlier)
- **Temperature Display:**&#160;`bubble_temperature`
- **Thermostat Control:**&#160;`bubble_thermostat`&#160;(presented earlier)
- **Trash Collection:**&#160;`bubble_trash`
- **Vacuum Status:**&#160;`bubble_vacuum`
- **Weather Display:**&#160;`bubble_weather`&#160;(presented earlier)
- **M&#233;t&#233;o-France Alerts:**&#160;`bubble_weather_alert`
- **Future Forecast:**&#160;`bubble_weather_forcecast`
- **Website Status:**&#160;`bubble_website`]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Mastering Time: Using Fake Timers with Vitest]]></title>
        <description><![CDATA[Level Up Your Timers Tests With Speed and Isolation]]></description>
        <link>https://brunosabot.dev/posts/2024/mastering-time-using-fake-timers-with-vitest/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2024/mastering-time-using-fake-timers-with-vitest/</guid>
        <pubDate>Wed, 12 Jun 2024 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[In the world of testing, controlling time can be a real challenge. Real-world timers, like `setTimeout()` and `setInterval()`, can be cumbersome when writing unit tests: without the right technique, you will introduce external dependencies on actual time passing that will make your test either slower, wrong or difficult to understand.


This is where Vitest's fake timers come in, giving you the power to manipulate time within your tests for a smoother and more efficient testing experience.


_At_ [_PlayPlay_](https://playplay.com/)_, we create high-quality software while prioritizing efficient development practices. We leverage innovative tools like Vitest's fake timers to write faster and more reliable tests, ensuring exceptional software from the ground up._


# Why Fake Timers?


Imagine testing a function that debounces another one after a 2-second delay. Using real timers, your test would have to wait for the full 2 seconds to pass, making the whole test scenario longer by these 2 seconds. This is slow and inefficient, and even more when you're dealing with multiple timers or complex timing interactions. Hopefully, fake timers are here to allow you to:

- **Speed up tests:** Advance the virtual clock by any amount, making tests run significantly faster. This is particularly beneficial for tests that involve waiting for timers to expire or simulating longer time intervals. Vitest prioritizes test speed. Fake timers become even more crucial when dealing with functions that rely on timers. You can avoid waiting for long intervals, keeping your tests lightning fast.
- **Isolate functionality:** By removing reliance on external timers, you ensure your tests focus solely on the code you're testing. This eliminates external factors that could potentially cause flaky tests and makes it easier to pinpoint the source of any issues.
- **Simulate specific timeouts:** Test how your code behaves under different time constraints. Fake timers allow you to create scenarios with specific delays or timeouts, helping you ensure your code functions as expected in various situations. You can check something&#8217;s state just before the timer is executed and right after.

# Getting Started with Fake Timers


Vitest provides the `vi.useFakeTimers()` function to enable fake timers: this mocks out the behavior of `setTimeout()`, `setInterval()`, `clearTimeout()`, and `clearInterval()`. 


You can call this method globally, before each test or on-demand. Here is an example:


```javascript
import { vi, beforeEach } from 'vitest';

beforeEach(() =&#62; {
  vi.useFakeTimers();
});
```


If you chose to do it on demand, you will need to restore the real behavior to ensure subsequent tests don&#8217;t inherit the fake timer behavior. Here is an example:


```javascript
import { vi, afterEach } from 'vitest';

afterEach(() =&#62; {
  vi.useRealTimers();
});
```


# **Basic Usage**


Let&#8217;s start with a simple example. Imagine you have a function that uses **`setTimeout()`** to execute a callback after a delay:


```javascript
function getDelayedGreeting(callback) {
  setTimeout(() =&#62; {
    callback('Hello, World!');
  }, 1000);
}
```


To test this function with fake timers, you can write:


```javascript
import { describe, expect, test, vi, afterEach } from 'vitest';

afterEach(() =&#62; {
  vi.useRealTimers();
});

describe('Given the getDelayedGreeting function', () =&#62; {
  describe('When we wait 1s', () =&#62; {
    test('Then it calls the callback with the right message', () =&#62; {
      // Arrange
      vi.useFakeTimers();
      const callback = vi.fn();
      getDelayedGreeting(callback);
      
      // Act
      vi.advanceTimersByTime(1000);
      
      // Assert
      expect(callback).toHaveBeenCalledWith('Hello, World!');
    });
  });
});
```


In this test, **`vi.advanceTimersByTime(1000)`** fast-forwards the timer by 1000 milliseconds, causing the **`setTimeout()`** to fire immediately. The **`expect()`** assertion then checks if the callback was called with the correct argument.


The true power of fake timers lies in their ability to ensure that time advances exactly as expected in your tests.. We can write the non-passing test as easy as the previous one:


```javascript
import { describe, expect, test, vi, afterEach } from 'vitest';

afterEach(() =&#62; {
  vi.useRealTimers();
});

describe('Given the getDelayedGreeting function', () =&#62; {
  describe('When we wait 0.999s', () =&#62; {
    test("Then the callback hasn't been called yet", () =&#62; {
      // Arrange
      vi.useFakeTimers();
      const callback = vi.fn();
      getDelayedGreeting(callback);
      
      // Act
      vi.advanceTimersByTime(999);
      
      // Assert
      expect(callback).not.toHaveBeenCalled();
    });
  });
});
```


# Advanced Usage


When writing tests, we will put a great importance to having the right situation at the right time. If the previous example is working, occasional delay could occurs and make the test flaky. So to ensure nothing more will happen with time passing, we would ideally cancel every running timer.


That&#8217;s where `vi.clearAllTimers()` can help us: by cancelling everything that is running, we make sure that nothing will prevent our test to work as expected. Here is the previous example improved:


```javascript
import { describe, expect, test, vi, afterEach } from 'vitest';

afterEach(() =&#62; {
  vi.useRealTimers();
});

describe('Given the getDelayedGreeting function', () =&#62; {
  describe('When we wait 0.999s', () =&#62; {
    test("Then the callback hasn't been called yet", () =&#62; {
      // Arrange
      vi.useFakeTimers();
      const callback = vi.fn();
      getDelayedGreeting(callback);
      
      // Act
      vi.advanceTimersByTime(999);
      vi.clearAllTimers();
      
      // Assert
      expect(callback).not.toHaveBeenCalled();
    });
  });
});
```


This ensures that any timers scheduled after the specified time advancement with `vi.advanceTimersByTime()` won't interfere with the assertion, making our tests safe.


Using `vi.clearAllTimers()` after `vi.advanceTimersByTime()`ensures that no timers scheduled after the specified time advancement will interfere with the assertion. This makes your test more robust and less susceptible to unexpected timeouts caused by lingering timers.


---


But sometimes, what we want is more than just advancing time: we want to make the timer to be executed no matter how long it lasts.


Let&#8217;s imagine a game that tries to improves your reflexes: the callback will wait a random timeout before calling the callback where a mystery number will be display for you to enter. The method would look like:


```javascript
function getMysteryNumber(callback) {
  const number = Math.floor(Math.random() * 10);
  // Get a delay between 1 and 6 seconds
	const delay = 1 + Math.random() * 5000;
  setTimeout(() =&#62; {
    callback(number);
  }, delay);
}
```


To be sure that the method is working, we can use `vi.runPendingTimers()` to execute the timeout no matter the delay:


```javascript
import { describe, expect, test, vi, afterEach } from 'vitest';

afterEach(() =&#62; {
  vi.useRealTimers();
});

describe('Given the getMysteryNumber function', () =&#62; {
  describe('When we wait the delay to be completed', () =&#62; {
    test('Then the callback is called with the mystery number', () =&#62; {
      // Arrange
      vi.useFakeTimers();
      vi.spyOn(Math, 'random').mockImplementationOnce(() =&#62; 0.5);
      const callback = vi.fn();
      getMysteryNumber(callback);

      // Act
      vi.runOnlyPendingTimers();

      // Assert
      expect(callback).toHaveBeenNthCalledWith(1, 5);
    });
  });
});
```


Here, we use `vi.spyOn()` to stub the `Math.random()` function and mock its behavior to return a specific value (0.5) for the delay. This allows us to control the randomness and ensure a consistent delay of 5 seconds in the test.


Now, no matter how long the delay is supposed to be, the test will be executed quickly.


---


But what if you have an interval timer that stop after a specific amount of time? For example, you have a method that counts up to five seconds:


```javascript
function startCounter(callback) {
  let count = 0;
  const intervalId = setInterval(() =&#62; {
    ++count;
    if (count === 5) {
      callback('Done!');
      clearInterval(intervalId);
    }
  }, 1000);
}
```


For interval timers that complete after a specific duration, you can use `vi.advanceTimersByTime()` to advance the virtual clock by the expected interval duration. This approach often provides more control over the test execution. However, `vi.runAllTimers()` is a viable alternative when you need to ensure all timers, including intervals, are run to completion. Here is an example:


```javascript
import { describe, expect, test, vi, afterEach } from 'vitest';

afterEach(() =&#62; {
  vi.useRealTimers();
});

describe('Given the startCounter function', () =&#62; {
  describe('When we wait 0.999s', () =&#62; {
    test("Then the callback hasn't been called yet", () =&#62; {
      // Arrange
      vi.useFakeTimers();
      const callback = vi.fn();
      startCounter(callback);
      
      // Act
      vi.runAllTimers();
      
      // Assert
      expect(callback).toHaveBeenNthCalledWith(1, 'Done!');
    });
  });
});
```


# Conclusion


Fake timers in Vitest provide a robust way to test time-dependent code : they allow you to write faster, more reliable, and more focused unit tests.


By simulating the passage of time, you can write tests that are both fast and deterministic. Whether you are dealing with simple timeouts or complex interval logic, Vitest&#8217;s fake timers have you covered.


With this guide, you should now have a solid understanding of how to use fake timers in your Jest tests. Experiment with these techniques in your own projects to achieve more effective and maintainable tests. Happy testing!  For further details and advanced usage, refer to the Vitest [documentation](https://vitest.dev/advanced/api.html).]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Improve Performances With Dynamic “content-visibility”]]></title>
        <description><![CDATA[Using the power of the content-visibility CSS property on dynamic sized elements]]></description>
        <link>https://brunosabot.dev/posts/2023/improve-performances-with-dynamic-content-visibility/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2023/improve-performances-with-dynamic-content-visibility/</guid>
        <pubDate>Mon, 26 Jun 2023 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Initial considerations


This blog post present few technologies that are not available on every browser. This should not be considered as the solution you can use to solve all your performances issues, but as the solution to improve some of your users' experience.


Also, you should consider using it more as an experiment than an ultimate performance savers that can help you understand how the rendering of a browser is working and what can be controlled on it.


Finally, the code could also be improved a lot by doing several enhancements -performance or features- to make it production ready. I use it on some websites, but it hasn't been tested at scale.


# The `content-visibility` CSS&#160;property


When we have a big page with a huge amount of data, the browser will struggle on one of the steps it has on displaying the page because it asks for a lot of work.


The browser will go through the following step:

- Loading
- Scripting
- Rendering
- Painting

The bigger the page is, the slower these operations will be. But at this game, the **rendering** step is definitely an Achilles&#8217; heel for everyone looking for speed. The browser makes a lot of calculations here to prepare the painting, and the more the page is complex, the more time it takes.


Unlike a house where you can do the painting once the construction is complete -and maybe once in a while for a deserved refreshing-, a browser has many reasons to make **a lot of renderings over time**. JavaScript DOM modifications, scrolls, dynamic carousels and so on.


To help developers taking more control over this, Chromium has introduced a new CSS property call `content-visibility` that aims to solve this performance by **skipping this rendering** part when it is not necessary, mainly because the concerned block is out of screen, which, in our single page applications, can happen a lot: have you ever see an e-commerce website without a vertical scroll?


This newly introduced property takes 3 possible values that are `visible`, `hidden` and `auto`. As you can imagine, the first one, `visible` exist to tell the browser that a block should always be rendered, no matter if it is on-screen or not, while the `hidden` value always hides the element from rendering. Since we want to improve our performances, the last one is the one that is the most interesting for us: it will make the browser looking at the supposed position of a given block and decide by itself if the block should be rendered or not.


## How does it&#160;work?


Setting this CSS property to `auto` will prevent the browser to execute the rendering part of the displaying, saving a lot of execution time (rendering + painting) on all the hidden elements of the page. Then, as soon as the elements appears on the screen, the rendering will be made, and you can see your content properly.


However, if it hides the rendering, you should not be concerned about SEO issues or having accessibility consequences: all the document model -and therefore the accessibility tree- is available: web crawlers will not suffer for any issues and the screen readers will work as expected. You should also remember that this CSS property is experimental, so old-fashioned system will simply ignore it until correctly implemented.


Finally, once the element quits the screen, it is removed from your page rendering. It will then save another list of potential heavy calculations, such as if you have `@keyframes` animations, for example.


## Setting the block&#160;size


As the content is not always rendered on your page, there is one thing that might look a bit strange&#160;: your window height will not be the full-rendered one, so scrollbars won't be able to so its proper height. With content removed from rendering as it leaves the page, this issue is not only a problem at the bottom, but also in the top: the upper part of the screen is removed from the page height as you scroll it out of the screen.


So, to make sure the space is reserved, you can use another CSS property there reserve this very space when using the `content-visibility` CSS property: it is `contain-intrinsic-size` that you can use to set the width and height of the elements. `content-intrinsic-size: 400px 300px` will tell the browser that the associated block is 400px width and 300px height.


## But?


If you want to display a card, it is very easy to use as you have fixed dimensions, but sometimes, you might not know initially the size of the element that you are not rendering, such as in a blog post: paragraphs have various sizes, code blocks can also have differences in dimensions. And, blog posts are likely to be long and complicated -hello syntax highlighting on code blocks-.


It is a shame for our performances that won't have the amazing benefit of this powerful feature.


Hopefully, there are a few other amazing APIs in the browser that can come to the rescue!


# The `IntersectionObserver` JavaScript object


The `IntersectionObserver` is an API that detects when a block gets visible or leave the screen viewport, which is pretty the same as, &#8291; but`content-visibility` this time, it is JavaScript, so it gets more interactive and customizable.


It has a lot of awesome usages, such as loading the next or previous pages in an infinite loading page as soon as the footer get into the viewport, pause a video if you scroll it out of the screen or lazy loading images -even tho you should use `loading="lazy"` instead.


## How does it&#160;work?


To use an `IntersectionObserver` you will need three things:

- A callback function that will be executed as the element enters or leave the visible part of the screen. This is where your magic will happen.
- An observer object that will handle the configuration of observation, such as the sensibility of the detection. It also means that you can use the same configuration object for every block of your code you want to observe, reducing the impact on the memory.
- An attachment to a DOM object that we will observe. In our scenario, it will be the block with a dynamic height on which we can&#8217;t use the `content-visibility` property.

Enough with the worlds, here is a very simple code example of how to get started:


`gist: brunosabot/09ea13cdb5356a43bf46c14fe525c538`


## Deep dive into the callback&#160;method


As you saw in the previous gist, the callback method takes two parameters.


The first one is the list of intersections that have just occurred. It is an object with many parameters that might be used in different use cases, but we can focus on the two most important for today:

- `isIntersecting` which is a boolean set to `true` appears according to the configuration `false` otherwise. That&#8217;s the only information useful in our use case, as we want to know if the element has reached the viewport and thus has been rendered and now have a real height.
- `target` which is the element that is currently intersecting. It will be the same as the DOM object we listened, but since a single `IntersectionObserver` can be used many times, this property is more reliable.

The second parameter is the observer instance. It allows us to start listening to another element if we are making an infinite loading system, but in our case, it gives us an opportunity to stop the observation as soon as we have the height collected.


Now, let&#8217;s see how we can link it to the `content-visibility`feature.


# Handle blocks with dynamic&#160;height


Linking `IntersectionObserver` and `content-visibilty` is now very simple: we have every tool we need, and we just have to add the smart behavior in the callback method to make an overall improvement on our webpage:

- First, we create an IntersectionObserver to detect visibility changes of a given block. When the detection is done, we will get into the callback method.
- Inside the callback method, we can collect the given element actual size since it has been rendered by the browser. It is as simple as `const { height } = entry.target.getBoundingClientRect();`
- We can now set the CSS properties on the object. It is a one-liner: `entry.target.style.containIntrasicSize = `${height}px``&#160;. There is no need to set the `content-visibility`: it should have already been assigned in your CSS to make this work. If you haven&#8217;t already, you would lose the benefit of make the rendering part of delayed.
- Finally, since the height is now assigned, we don&#8217;t need the observation anymore so we can stop the observation on this block

Here is a code example merging these two features:


`gist: brunosabot/e402822e97a6166b41d219576f7fefd5`


## Handling responsiveness


There is one final touch we can bring to our code to make it more production ready: if the window gets resized, some blocks might have a changed height and thus our assigned value is not relevant anymore. And that's the beauty of responsive designs.


To ensure the height is always the right one, we can just observe the `resize` `window` event, remove the assigned height and bring the observer back to business. As soon the element will cross the visual part of the page, its height will be recalculated.


`gist: brunosabot/4a77e01ac151a8af3612a70d5befe3f6`


# Conclusion


We now are ready to make our pages a lot faster! With these two light APIs that allow the developer to be more in control of what the page needs to display at anytime. We can benefit from the power of `content-visibility` event on blocks with a dynamic height!


If, like me, you are working with frameworks, you can find at the end of this blog post a React hook and a Vue composable you can reuse in your applications.


Feel free to give me feedback on your Core Web Vitals improvements with this snippet!


## React hook


`gist: brunosabot/6115b209b33cdf7bc039f1726d8ac6d4`


## Vue composable


`gist: brunosabot/3583aba5b9e3af505c91d854f9f6d647`]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[JavaScript Spread and Rest Operators: When To, Why To, and How to Use Them]]></title>
        <description><![CDATA[Understand two of the most useful JavaScript features]]></description>
        <link>https://brunosabot.dev/posts/2022/javascript-spread-and-rest-operators-when-to-why-to-and-how-to-use-them/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2022/javascript-spread-and-rest-operators-when-to-why-to-and-how-to-use-them/</guid>
        <pubDate>Fri, 01 Apr 2022 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Ecmascript has introduced the rest and spread operators a few years ago. It is a powerful tool that helps a lot our code to be more concise and cleaner.


However, it is not that easy to understand how it works at first and what is the main reason they do not have the same name.


In this blog post, I will explain the naming, how it is working, and in which situation it is a time saver.


# Assignment shortcuts


Before we talk about the rest operator, you need to understand a new variable assignment method to shorten the code declaration.


It consists of using an array or an object on the declaration part of an assignment.


The code looks like this:


`gist: brunosabot/9b3632841673b46370817b038f3d0779`


As you can see, we are creating an array naming the indexes directly in the assignment part. This code is actually a shortcut and the same of the following code:


`gist: brunosabot/3f74a2dafb703f9567861db64028f803`


If this code is pretty simple, it might be a bit longer and therefore complex- when having a dozen of items in the array.


This is working the same with objects. Here is the code:


`gist: brunosabot/92f610ba615b0c6f04b9b07c71c5297d`


The only difference with the array is that the naming is very important since we are extracting the object from its keys.


If for any reason, you need to change the name (it could be a duplicate key), you can use the `:` operator to change the name. Here is an example:


`gist: brunosabot/49aa78f8bc47f9fca3f568aa440b20ab`


As you can see, we have a duplicate red key that we had assigned to a `frenchRed` variable and a `belgianRed` variable.


If you acknowledge this, you can now start the next part about the rest operator, that uses this variable assignment method.


# The rest&#160;operator


The name has been chosen wisely, the rest operator is a method to select the rest of the data that hasn't been already assigned.


This operator uses three dots `...` as a symbol to tell the JavaScript parser that we are using the rest feature.


But one point is really important to understand: the rest operator is only used on the assignment part of a JavaScript code. We saw the new assignment method in the previous chapter, but the reason it is important to link rest and assignment will be discussed later.


The rest operator works on the array since the beginning of the feature introduction, and a bit later on objects. If you managed to make it work on other JavaScript types, you are using coercion which I recommend you not to use. But this might be the content of another blog post.


Now that the basics are ok, let's see some code on how it looks:


`gist: brunosabot/1953d864555fcbdadd1d1deb819c6468`


As you can see in the previous code, the assignment is the same than the first chapter, but we add a single variable name with three dots `...` instead of the whole elements left.


This will tell the JavaScript parser to create an array with anything else in the first assignment, and an object with anything else in the second one.


# Why do we need the rest operator?


We mainly need this operator to extract a part of the array for a specific behavior and use the rest for something else.


Imagine you have an array with every soccer player of a team and you want to separate the goalkeeper from the field players, so you can give them the instruction to go full attack. You can do it with the following code:


`gist: brunosabot/59e4000e5ef58b486042bd89bb25ac84`


Of course, you can select two items if you want to keep a defender in the field:


`gist: brunosabot/2345665f931723d0a55e9af04944bf5e`


You can add as many variable declarations in the assignment, but you can not skip some variables in the rest argument. For this, you will need the spread method we'll see later.


For objects, the rest operator is very useful when you want to remove an element from the object you want to forward.


Imagine you are developing a Node.js service to manage users, and you want to send to the client everything from the object except the password and the password's salt... In this case, the rest operator will help you.


Here is the code:


`gist: brunosabot/52db659b2f4df71c6e16de618d132582`


With this, you can simply return the right data for the client's request


# The spread&#160;operator


Like the rest operator, the spread operator also uses the three dots `...` as a syntax.


The way to make the difference is to understand that the spread operator will be on the operation part of the variable assignment.


The goal is pretty the same as the rest operator, but instead of recovering the rest of a variable, we are adding the rest of another variable to a declaration.


Code speaks better, here is an example of how it works:


`gist: brunosabot/d18b75153aecd5c27602c8366d24083a`


On the previous example, we have combined two objects with the rest operator. If it's still not clear, here is an example that could help.


`gist: brunosabot/dac18f7eb862b33bcb0326d5a2fae1c5`


The syntax is not valid JavaScript, but it illustrates what is done under the hood.


As seen in that example, using the spread is kind of removing the brackets from our variable. Since it is something invalid in JavaScript, we need to put that in another array, making an array duplication in the meantime.


In arrays, the position of the rest operator is very important as it places the elements in the specified position.


Here is an illustration of that:


`gist: brunosabot/def326b3fda8c91f949dcc2ea5d42207`


In the previous sample, I also have another item in my array which is non-spread operator data. It is a perfectly valid thing to do if you want to complete your object with a unitary value. It could be having an array with the last execution result, complete with the previous results.


For objects, the spread operator works the same. However, as objects are key value items, the order does not really matter but you need to make sure your keys are not duplicated or they will be erased.


Here is an exemple:


`gist: brunosabot/1c90778188505dc22d9304ca647c6777`


As you can see in the previous example, the position of the spread operator is very important in case of duplicated keys.


As it's the case for arrays, the spread operator removes the braces from the object, but since it's invalid syntax, we are putting it back in another object duplicating it at the same time.


`gist: brunosabot/d7a818075cc5fcd4261527be3f83c3a5`


# When do we need the spread&#160;operator


As we have seen in one of the previous examples, the spread operator is mainly used to merge arrays from different sources or having defaults key/values when using objects.


You might find plenty of other usages while developing your JavaScript application.


# Combining rest and spread and&#160;more


Since it rest and spread are not on the same part of the assignment, you can combine both their usage in the same line of code. You should however take care of not using this to make your code harder to read: it will be counterproductive.


You can also apply the spread operator on a function return, as long as the value returned matches an array or an object depending on your usage.


Here is an example:


`gist: brunosabot/393fe888dadb25cd11461a01b2b39325`


# Conclusion


Rest and spread operator are very powerful features you can bring to your web application starting today.


If it is a bit hard to understand at first, it will make your code cleaner and more robust.


---

&#62; Want to Connect?  
&#62;   
&#62; If you want to learn more about JavaScript and React, feel free to follow me on [Twitter](https://twitter.com/brunosabot).]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Loading Gists in a NextJS Application]]></title>
        <description><![CDATA[A tutorial on how to load GitHub Gists from inside markdown pages of a NextJS blog]]></description>
        <link>https://brunosabot.dev/posts/2022/loading-gists-in-a-nextjs-application/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2022/loading-gists-in-a-nextjs-application/</guid>
        <pubDate>Tue, 01 Mar 2022 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Introduction


All my blog posts are available on my [personal website](https://brunosabot.dev/). Under the hood, they are saved as markdown files.


It allows me to easily write the posts, but when I&#8217;m cross-posting on other platforms, I don&#8217;t have the luxury to get the syntax highlighting and the right colors for the code snippets.


To make it prettier on every platform, I&#8217;m using Github Gists. It renders well everywhere, but I don&#8217;t want to have Github&#8217;s code blocs styles on my blog but I prefer something that match my website design. To do so, I have to adapt the markdown parser I use to load and render the code from gists in the way I prefer.


In this post, we will see how to create a blog post using markdown files, how to create and integrate a remark plugin, highlight the recovered code it with `react-syntax-highlighter` and keep the performances as good as possible.


# Initial Situation: Loading Markdown in a NextJS Page


Before getting into the code for loading gists, we first need to make the blog to load markdown files. To do so, it needs two different items:

- A markdown file containing the blog post
- A NextJS page containing the markdown loading, parsing and rendering

Let&#8217;s start with the markdown file


## What Does The Markdown File Looks Like


The markdown file is separated in two different parts: the metadata and the content itself.


The metadata are here to give informations on the author, the date, the language used (I have posts both in french and english), the URL path, tags, etc.


These informations are isolated from the content by three dashes &#8212; at the begin and three and the end.


Here is a light sample of what a blog post can look like:


`gist:brunosabot/69e418227ae80e3844348a522fa9fbbb`


The file is very simple. If you are not familiar with markdown, you can [read this doc from Github](https://docs.github.com/en/get-started/writing-on-github/getting-started-with-writing-and-formatting-on-github/basic-writing-and-formatting-syntax) to get started: you will learn how to create lists, links, add image and more.


In the reality of my bog, I have more metadata fields to handle canonical links, image, tags and more. I removed them from that post as it is unnecessary noise for its topic.


## Loading The Markdown File


The first part to render a markdown file is to load it. We are using basic NodeJS code to:

- Lookup all files in a folder
- Filter the md/mdx files only
- Load the file

It might be overkill, but on my blog, I need to get every posts to find the most similar posts of my current article, based on a calculation on every common tags they have. I will have to load every post to find out anyway.


Here is the code:


`gist:brunosabot/d1415002ea8e916cee28ca5c11ef8803`


If you are creating a simpler blog, you might prefer to directly load the right file instead of all of them. To do so, you just need to use the `fs.readFileSync` function to get your file.


Now the posts are loaded, we need to search inside the file which one is the right one for our current page.


To do so, I&#8217;m looking for the post with same path as requested, but I first need to parse my markdown for metadata. I&#8217;m using a package called `grey-matter` to get them.


Here is the code:


`gist:brunosabot/c5faec177d6a35826d65a3c759ee6458`


The `loadGreyMatterPost` is a function returning a file representing our markdown at the grey-matter format. We still have to transform it into an HTML code to use it. As explained earlier, I am also returning all the posts to make matches based on the tags. It will however not explained in this post.


Since I&#8217;m using NextJS, I can now use the package `next-mdx-remote` which can make the transformation from the grey-matter format to a MDX one.


Here is the code:


`gist:brunosabot/19298ac37429acba5234b7d6e416910b`


## Adding The Loaded Data Inside a NextJS Route


To avoid enormous and slow payload, the loading part is added inside the NextJS route&#8217;s `getStaticProps` method. It will only parse the files at the compile time, which is OK with markdown, and serve static pages the faster possible.


Here is the code:


`gist:brunosabot/4653bf5059006b804ef3da778c04de51`


## Display the blog post in the page


Now we have all the required informations, we can include them in the NextJS page.


It will need two external components:

- `MDXProvider` which is a component that can handle defaults informations for every children markdown document
- `MDXRemote` which transform the MDX content to HTML React components

Here is the code:


`gist:brunosabot/dd0de643a61b47668b47debf6858feb8`


And it is done! We are now able to write markdown blog post and get them rendered as HTML in our NextJS application!


# Creating a Remark plugin


## The AST transformation


A remark plugin is a function that return another function. That last one gets an AST -Abstract Syntax Tree- representing the markdown document.


The AST can get numerous fields, but here is a simple typescript interface of how it is structured:


`gist:brunosabot/bd30f8a6c30d230c3e7a8dc12789f1d6`


When using markdown, the AST for code does not have children or attributes, while the paragraph AST does not have any value. To understand how it is built, imagine the following markdown:


`gist:brunosabot/a514c47f2b011f3465815cb8cac489a7`


The associated AST will be:


`gist:brunosabot/c3ef93154908d6fc7ec9e559dd80227f`


With this in mind, we can think about how we are going to include our gist.


## Fetching a Gist From Github


I chose to include the gist in an inline block code, with a prefix `gist:` and then the username and ID. It will look like this:


```plain text
gist:brunosabot/00000000000000000000000000000000
```


When rendering this content, the generated AST will be composed of a `paragraph` node with an `inlineCode` child. What we need to do is to replace the paragraph the loaded code from Github.


As we need code from Github, we will also need to fetch the gist. A gist can be made of one to several files: we will first need to iterate on the file list then load each one of them.


Here is the code:


`gist:brunosabot/0452d814c076b772dbb91ada500e999d`


The first fetch query gets a JSON containing metadata for the gist, especially the file list in the gist.


When the list is recovered, we iterate them to load as text the code associated to every file.


## Visiting And Updating The AST


The gist plugin should alter the content of the original AST. We are going to visit the document nodes and update it as we want.


To visit the different nodes, we use the `async-unist-util-visit` package. it will apply a method on every AST child that match the requested type.


As explain before, we are going to look for `inlineCode`, starting with `gist:`, inside a `paragraph` node, then parse it and load the gist.


But let&#8217;s focus on the visiting code:


`gist:brunosabot/7c438152e946327b4493ca62e562ff69`


One important thing to notice is that the AST must be mutated and not returned. Since `visit` is a promise but does not wait for the callback to resolve, we need to use a hack consisting on adding the work in a promise list and wait them to resolve before the plugin method to end.


In the code snippet:

- The `node.children.some` method is looking for a gist snippet in the code
- The `Object.assign` method is the way to update the content of the object without loosing the reference
- The `delete` is not mandatory, but I like to keep a clear object with only the required fields.

In the plugin, we are using a `loadAndTransformGist` method that is called when we detect a gist snippet in the code.


As it written in the function name, this method will load the gist and make the proper transformation to be included in the AST.


Here its code:


`gist:brunosabot/cbfecba9765c20c48a25815df7cb2578`


In this snippet, the first part is to check it the snippet is valid, which is basically checking if the AST value is empty or not.


I could make few more checks, including one to validate that the gist value is correct. I choose not to since I&#8217;m often checking the rendering of my post: I can see very quick when the ID is wrong since the app will either crash or don&#8217;t show anything.


I&#8217;m then extracting the gist ID, load it with the previously created `loadGist` method.


Then, I have two possibilities:


First, there is only one file in the gist. I will just replace the current paragraph with a bock sample. The `getGistAST` will give me the code AST that I will return for the calling method.


Second, there is multiple files in the gist. I will iterate on them and add them as children of the paragraph node.


The `getGistAST` is basically a mapping method with the following code:


`gist:brunosabot/98cf6ddccc3cd6d3d0c5e5efb0f80f1d`


Translated to english, this AST node is a `&#60;Gist&#62;` component, with:

- A file attribute representing the file name
- A code attribute which is the actual code
- A lang attribute containing the file language, calculated from a mapping method

And here is the mapping method:


`gist:brunosabot/8f7dcb1cb5f595b5c754b66c8423cbf8`


And everything is done, we just need to update our `transformGreyMatterToMDX` method, adding the plugin:


`gist:brunosabot/5dcd2b0374bb7445b1248d3ec0fdad78`


# Using a Custom Rendering Component


Now the markdown is able to read gist snippets, there is a last step to make the actual code rendering: we need to create a `Gist` component to make the proper display.


To do so, I will use the [React Syntax Highlighter](https://www.npmjs.com/package/react-syntax-highlighter) package.


Here what the component looks like:


`gist:brunosabot/983ed6698517b94caff8f884a3f56dd5`


We first have a single div, if the file name is available to display it.


Then, we use `Prism` from `react-syntax-highlighter` with the following attributes:

- `language`: the programming language the snippet is on. If not available, we set `text` which a a default and non highlighted language
- `showLineNumbers`: this display line numbers on the side. If text, it is better not to have them
- `wrapLines` and wrapLongLines: activate, when the code is `text` wrapping the lines. I found it not easy to read on highlighted code.

To be recognized, this Gist component needs to be included in the components given to the `MDXProvider`


Here the code:


`gist:brunosabot/4fc512dc3bdc5b3ac637f942c26c0466`


In bonus, you can see I&#8217;m rewriting the img component to add the `loading="lazy"` attribute. I will help my blog post to have better performances.


# Keep The Performances As Good As Possible


One problem with React Syntax Highlighter is that it will load a lot of language you might never need.


Hopefully, there is a way to use a lighter version of the library, but it require us to load the languages manually.


Here is the `&#60;Gist&#62;` component code with the loading system:


`gist:brunosabot/9b54f6191dd73013648e75c954b3f32e`


# Conclusion


Creating a Gist plugin for a NextJS blog requires us to know about and manipulate the AST format which might not be trivia.


I hope this post can help you understand how it works and how you can also add your gists files right into your blog.

&#62; If you want to learn more about JavaScript and React, feel free to follow me on Twitter.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Fetching data with React]]></title>
        <description><![CDATA[From custom made code to powerful libraries]]></description>
        <link>https://brunosabot.dev/posts/2022/fetching-data-in-react/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2022/fetching-data-in-react/</guid>
        <pubDate>Tue, 01 Feb 2022 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Introduction


When we create a React application, there are a lot of chances you will have to fetch data from a remote server.


Depending on your project, you will probably have to make simple calls or use advanced techniques to get your data cached or up to date.


In this blog post, we will discuss custom-made data fetching but also have a quick preview of [React Query](https://react-query.tanstack.com/) and [SWR](https://swr.vercel.app/). Let&#8217;s take a tour of these common techniques so you can pick the right ones for your use cases.


# Basic Data&#160;Fetch


To explain how to make a custom data fetching, we will pass through a few steps to understand how to make the code robust enough, based on what it can be to think about the perfect loading system.


If you are not confident enough to manage it on your own, I recommend you go directly to the last sections on SWR and React Query.


# Using the JavaScript Fetch&#160;Method


To get a basic data fetching working, we only need a place that will contain the recovered data and a method to make the actual fetching.


Speaking of the code, it consists of the `useEffect` hook to make the data fetching and a `useState` hook that will store the data as soon as the request end.


`gist:brunosabot/70f06dc89a783020c6898c1873f4972c`


As you can see, you can get your data from your remote server in just a few lines of code. Gathering data is as simple as that with React.


# Separating Concerns


The previous code was very simple, but one common principle in web development is the separation of concerns which we didn&#8217;t really respect in the previous section with the two hooks.


There are plenty of ways to make it done. For example, I will use a Provider component and the React contexts to handle this. You can find out more in my previous article [How I dropped Redux for the Context API](https://betterprogramming.pub/how-i-dropped-redux-for-the-context-api-7338d481e179).


To follow this way to separate concerns, I will now wrap the displaying component into another one that will manage the data fetching. Here&#8217;s the code:


`gist:brunosabot/6b320eb96713eb906447d050c4d41230`


Our rendering code is now a bit cleaner since the logic has been extracted to another component in charge of the logic.


You can see that I choose to use a loadData callback along with the useEffect hook this time. This is because I consider improving the method with additional parameters&#8202;-&#8202;not in this tutorial though&#8202;-&#8202;to manage pagination, revalidation, and more.


In the same way, I have encapsulated the data inside a subobject `values`, to be prepared to support another sub-object `actions` for manual reload and more.


# Adding Loading and Error&#160;States


In many applications, we want to show the user that we are currently loading the data or if we encounter an error.


To do so, we just have to add two boolean states corresponding to the loading and the error.


These states are meant to work this way:

- By default, the loading state should be false since there is no operation made
- As soon as we launch the data loading, the loading state should switch to true
- The loading state should get back to false as the request end
- By default, the error state should be false since there are no errors yet (and hopefully, ever)
- As soon as we launch the data loading, the error state should be reset to false to remove an older error
- The error state should switch to true if the loading goes wrong

Here&#8217;s a code sample:


`gist:brunosabot/a48185e09bac40b2044a5f64ceb91642`


Now, our application reports the loading and error states with a custom message to the user.


This solution stays pretty basic, but you are free to add additional data, such as a specific message for the error, better loading, or a skeleton of the page to make an even better interface.


A common mistake made on a lot of websites is to give no intel on what happened on a website. You can lose users because they think your app has crashed if there is no loading indicator, or they may think your service is simply not working if the error is not explicit enough.


My personal recommendations are:

- Add a skeleton of your page while loading the data
- If possible, show a loading progress indicator
- If the first point is too complicated, add a spinner or a text indicating the data is loading
- Cache the request to avoid unnecessary waiting from the server or propose a &#8220;Stale While Revalidate&#8221; behavior
- If you encounter an error, give your user precise information on what is going on., e.g., &#8220;Your data hasn&#8217;t been saved because it is not valid&#8221; or &#8220;We encountered a problem loading this product&#8230; Please try again later.&#8221;

# Factorize to&#160;Hooks


When developing an app, you will probably not have only one place where you will need to load data. Each one of your pages are candidates to fetch remote servers.


With the previous code, we can clearly see a lot of code that will be copied if we want to keep the same code structure, even if the only update we want to make is an URL change.


A good candidate to resolve this is to create a custom hook to contain the error, loading, and data state hook along with the data loading method. This hook will get an URL as a parameter, as shown below:


`gist:brunosabot/d9f87875cab301f8d2708700666f27aa`


Now, all the data fetching will be managed by the hook, and the provider code will be simpler to read.


Once again, this is a pretty simple use case, you might need to handle:

- Making POST request
- Adding, on a POST request, a body content
- Handle HTTP headers
- Manage authentication

# Do We Really Need the Separation Concern in a Component?


Our provider became a simple pass-through from the hook to the component and we can ask ourselves if it is still a relevant component to include in our code or if it is unnecessary.


I believe that the less component you have, the easier your code will be read by anyone else (validating the KISS principle). I choose then to remove the Provider part and only keep the view component and the hook. Here&#8217;s the code:


`gist:brunosabot/a0a860d5324c91f5519744d6cd3883b4`


Why have all these steps to get there? It is a pretty common mistake I saw in many projects to keep legacy code layers. I&#8217;m hoping that you will avoid these mistakes by seeing a complete rewrite of the code the more features you are adding to your data fetching.


Depending on my needs, I can also remove the `useEffect` part that could have been done here since we obviously always want to load the data straightaway.


# Using a Data Fetching&#160;Library


Writing data fetching is very simple, but there are many reasons where coding all by yourself could become a huge pain. The preview code we just wrote could be easy to imagine in your mind, but what if you need to:

- Add a query caching system
- Handle an always up to date data
- Debug your requests
- Handle pagination and infinite loading
- Keep data available offline

Could you picture all the code required in your head right now? I personally can&#8217;t, so I&#8217;m going to leave this to the greatest geniuses.


So our requirements give us a lot of work, not even including the code maintenance, and the security patches that will be required. Hopefully, there are a few open source libraries that already manage this for you, such as [React Query](https://react-query.tanstack.com/) and [SWR](https://swr.vercel.app/).


These libraries might be a (very little) bit more complicated to implement inside your apps than the hook we have previously coded, but they are also way more powerful.


Let&#8217;s see how we can start using them.


# SWR


[SWR](https://swr.vercel.app/) is a lightweight library developed by [Vercel](https://vercel.com/).


SWR will however not handle the request itself. You will need to create a `fetcher` method, but the code stays pretty straightforward, as you can see below:


`gist:brunosabot/6dd2b044779868123fdbdd7c8f6bfa94`


Almost all the logic we previously wrote ourselves is managed by the useSWR hook. Don&#8217;t think that the code magically disappeared!


You might ask yourselves why should we use SWR if we still have to handle the `fetcher` method? Because SWR has a lot of useful features including the following:

- It automatically caches your requests
- It handles React suspense
- It automatically revalidates data when focusing the window and/or on regular intervals
- It can manage pagination, SSR

# React Query


[React Query](https://react-query.tanstack.com/) is a little bit complicated to get started with: It will need a Provider component on the top of your application, combined with a query client.


Also, like SWR, the actual fetching is yours to make.


With that done, it will be as simple to use as everything we have covered so far, with only a different labelling system.


`gist:brunosabot/85be602e84f539e04c0d56eaba8893a8`


React query also have a lot of awesome features you can check in comparison to other systems, available on the [React Query website](https://react-query.tanstack.com/comparison), including:

- A powerful cache system
- Dedicated dev tools
- React Suspense support
- Auto-refreshing
- Pagination, SRR

# Conclusion


There are plenty of ways to load data in React&#8202;-&#8202;from managing our own code to using powerful libraries.


Personally, I would change the method I use depending on the size and nature of the project in the following conditions:

- When making a very small website with few requests, I will make my own fetching code (SWR and React Query came at a size cost)
- When the project gets bigger, I will go for SWR (Best size/features ratio)
- On big projects, I prefer to use React Query since it will cut me the work on many useful features (advanced features needed)

Thanks for reading. Please join me on [Twitter](https://twitter.com/brunosabot) if you want to learn other things about React and more.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Core Web Vitals Dashboard On Google Analytics]]></title>
        <description><![CDATA[Start a Core Web Vital Real User Monitoring on Google Analytics]]></description>
        <link>https://brunosabot.dev/posts/2021/core-web-vitals-dashboard-on-google-analytics/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2021/core-web-vitals-dashboard-on-google-analytics/</guid>
        <pubDate>Sat, 18 Dec 2021 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[[Core Web Vitals](https://web.dev/vitals/) are a set of well-known metrics used to measure your website performance. It&#8217;s often the entry point to investigating what is wrong with someone&#8217;s website and measure the progress made optimizing the overall speed and stability.


Some of you (or your clients or employers) might not have the budget to invest into the various existing performance monitoring tools, or maybe no time to test and integrate them to the website, but they have a [Google Analytics](https://analytics.google.com/analytics/web/) tracker set. With a few lines of code added, it is the perfect tool to start monitoring the performance experienced by the users when browsing the various pages of your web site.


# Including the tracking code


In my example, I will be using [Next.js](https://nextjs.org/) which is a React SSR (server-side rendering) framework with a lot of internal optimizations, plus wonderful components such as `&#60;Image&#62;` and `&#60;Script&#62;` to improve the scripts and images loading and delaying/scheduling.


To extract the tracking part, I first have a reusable `&#60;Analytics&#62;` component which is responsible for including the two tags required by Google Analytics, and I&#8217;m using Next.js environment variables for the customization part.


With the hypothesis I have in the first paragraph, you may not need this to be included since your client is likely to already use Google Analytics tracking, but you can use it on new projects or existing ones without tracking system.


`gist:brunosabot/8be487d97242450d26a0d0eae30538cf`


Reporting Core Web Vitals with Next.js also needs to add/update the `_app.tsx` file to add the metrics tracked and the page views tracked.


Once again, here is the code if you do not already have Google Analytics on the project.


`gist:brunosabot/167ef4e051d5a06664b6e4f458125a7e`


There are two parts in the file to consider here:

- a `reportWebVitals` method that is automatically used by Next.js when it gets a Core Web Vital metric. If you are not using Next.js, you should look at the [NPM web-vitals package](https://www.npmjs.com/package/web-vitals) to see the right method in your case.
- a MyApp component that includes the `&#60;Analytics&#62;` component presented earlier and a (bonus) hook to track the page change in Analytics. This one is to be used only if your client does not already have Google Analytics installed or you might duplicate the page change events sent to the server.

You now just need to add your UA value to the `.env` file or as an environment variable `NEXT_PUBLIC_GOOGLE_ANALYTICS` and you are ready!


# Viewing data directly in Google Analytics


After few days collecting, you will see the events (in green on the screenshot) appearing in Google Analytics in the Behavior (in blue) &#62; Events (in purple) &#62; Top Events (in red) &#62; Event action (in orange) menu.


![view-events-on-analytics.png](https://calendar.perfplanet.com/images/2021/bruno/view-events-on-analytics.png)


Viewing Core Web Vitals Events On Google Analytics


And voil&#224;, now you can report the real performance usage to your client directly in Google Analytics.


Going further by creating custom dashboards If the previous screen give us the metrics, it is still a combined view where the chart shows all the metrics combined.


To have a very specific view for LCP, FID and CLS, we can create custom dashboard by clicking on the Edit button on the top right od the screen.


Once in the creation page, we need to update three block on the page:

- The Dashboard title (in blue)
- The Tab name (in purple)
- The filters (in red), to Include / Event Action / LCP
- Then press the Save button and you are done for the first metric!

![creating-lcp-dashboard-on-analytics.png](https://calendar.perfplanet.com/images/2021/bruno/creating-lcp-dashboard-on-analytics.png)


Creating LCP Dashboard On Google Analytics


You now just need to reproduce the previous steps for FID and CLS and you will have 3 custom reports with the Realtime User Monitoring (RUM) for your users!


# Conclusion


Adding dashboard for Core Web Vitals in Google Analytics is an easy way to get a Realtime User Monitoring of your website performance.


If the created dashboard are too simple, you can go further thanks to Google Analytics power by adding second dimensions such as pages, device type, and even with conversion custom metrics.


Happy monitoring!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Build an Easy Popup System With React]]></title>
        <description><![CDATA[Demystifying the way to create a simple, customisable, and accessible popup system with React]]></description>
        <link>https://brunosabot.dev/posts/2021/build-an-easy-popup-system-with-react/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2021/build-an-easy-popup-system-with-react/</guid>
        <pubDate>Fri, 12 Nov 2021 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Concerns About Existing Systems


There are plenty of popup systems out there, but they usually don&#8217;t meet the high-quality requirement I have on user interfaces and development simplicity.


When I&#8217;m adding a popup into a website, it is important to me that the system is:

- **Simple to use**: as a developer, I don&#8217;t want to spend time creating tons of components and states just to activate a popup. A developer better spend their time on the domain specificities rather than brainless tasks
- **Customisable**: this is usually my main point of complexity since popup systems are almost always shipped with styled components, making it - harder to make them look as close as your UI Designer has imagined them.
- **Accessible**: Accessibility is usually created aside from the systems because it asks for more work, even if it doesn&#8217;t need that much work on it.

With these requirements, I always find it difficult to find a library with what I need and the blocking points are often too painful to be worked around.


Even if it might not be intuitive, the last standing option is to create our own system so that will ensure a perfect match with your needs


Enough speaking, let&#8217;s dive into a popup component system creation.


# What Are We Building


There are a few things we want in this popup system:

- A custom modal component that will be in charge of the popup style, including background, position, and a closing button
- An easy-to-use modal component with a simple toggle system that will be in charge of the functional part of the popup.
- A changeable state to make the CSS modal softly appear
- Support for people who need a browser with reduced motion
- Handling accessibility on the modal to tell people with disabilities the popup has appeared and where to click so the popup will be closed
- A clickable background overlay to close the popup as we click out
- Handle the escape key to close the popup

That&#8217;s a lot to do so we better get started.


# Requirements


The first thing to have a modal system is to have a modal root, where the system will take place. To do so, we just need to have a new `div#modal-root` element in our root document.


This part is important so the modal can be easily styled. With a separate root element, we are sure that the parent elements of the modal does not have styles that will make it harder for us to reach the perfect style.


To be sure that the modal will always be on top of the document, we just need to add the right `z-index` on the application root and the modal-root.


Also, since the modal behavior is to be opened and directly occupy the whole browser&#8217;s page, we add an [ARIA live region](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Live_Regions) to the modal system so it can be announced to the user.


The aria live region is set to assertive because we want the readers to have the same behavior as the browser, which places the popup on top of everything else.


`gist:brunosabot/43bc900a8f1b02df53370c69dfe64780`


# The modal components


The modal component is split into three different components:

- A `ModalPortal` component that will link our modal to the `div#modal-root` element
- A `ModalView` component that aims to handle the visible part of the component
- A `ModalAnimated` component that will handle the popup domain and the CSS appearance effects of the popup system

## The ModalPortal component


The `ModalPortal` component exists to link our popup to the `div#modal-root` element that we have created. Here&#8217;s the code:


`gist:brunosabot/3110e062d9dc353ba3a47c28254ac6e1`


It is made of four sections:

- A `ref` corresponding to a simple `div` element, with the goal of holding the popup content. We do not use directly the root element so we are able to create two or more different popups if we want to stack them.
- A first `useEffect` hook to create the `div` element. This is a security to make the system work also on SSR systems such as NextJs or Gatsby.
- Another `useEffect` hook, to add the previously created `div` in the portal when active, and remove it when inactive. It will prevent the `div#modal-root` element to contain plenty of empty divs.
- The render part, which is null if neither the `div` element created does not exist or the popup is not currently active.

## The ModalView component


This one is basically a layout component so we can style the popup the way we want.


Even if I&#8217;m presenting only one template, you are able to use it for as many needs you may have such as:

- A popup system
- A designed replacement of the native `alert` and `confirm` modal
- A notification system
- Whatever else you can imagine

`gist:brunosabot/79742fa524c20dc28d3173c042d6e9f7`


The present component is just a bunch of native elements with some styles separated into two sections:

- An overlay button, so the popup can be closed when clicking out
- The popup content itself, including a close button

The two blocks are siblings because we don&#8217;t want the click event to propagate from one to the other.


For accessibility reasons, both the overlay and the close buttons are native button elements with an `aria-label` attribute.


In the CSS part, I use various positioning techniques that you are free to adapt depending on your needs.


## The ModalAnimated component


For the last part of the system, we need a component that will control the modal. Here&#8217;s the code:


`gist:brunosabot/fb6ef1864549f741938ad093bb8b0e1f`


This component has several tasks to handle:

- It has to load the ModalView component. By default, I chose to use the ModalView component, but I also give the component a prop to be able to change it
- It also has to manage the Modal portal component to include our content in the `div#modal-root` DOM element
- It gives us access to an escape key support to close the modal.
- Finally, it handles a nice but optional transition effect.

The CSS has a weird CSS Modules syntax to handle global classes, but it also uses the `prefers-reduced-motion` media query to shutdown the animation for people asking for it.


If the last part could be set globally for all elements, it is better illustrated in the component.


## The useEscape hook


To improve usability, we can add another great feature to our popup system by adding an escape listener that can close the popup.


To do so, there is a `useEscape(active, onClose);` code in the ModalAnimated component, but this is yet to be implemented. Here&#8217;s the code:


`gist:brunosabot/41f8acd09fee652c24d4575e29dd5e42`


The hook is quite simple, and it is made of two blocks:

- an `onEscape` callback that memoize the keyboard event by listening to the keyCode for the escape key &#8212; 27
- an `useEffect` method to bind it to the window document and unbind it as soon as the modal is unmounted

## The usage


The usage is pretty straightforward: we need the `ModalAnimated` component with two props if we want a custom ModalView component.


The content of the popup itself is just the children elements passed to `ModalAnimated`. I usually put the content inside another component to keep the page as light as possible. Here&#8217;s the code:


`gist:brunosabot/4ba4b450f769a15e52c5b3de3a078451`


# Conclusion


By creating three light components and a simple custom hook, we are able to get a very modulable and customizable popup system.


While it can still be improved, we have implemented a system that will make your UI designer happy, and it implements the accessibility basics.


Did we check all the initial requirements?

- Simple to use: yes
- Customisable: we can customise the view very easily
- Accessible: We do have a11y included in the code

Mission accomplished! Now it is your turn to use it and improve it in your projects.


Happy coding!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[A Discord bot for Home Assistant]]></title>
        <description><![CDATA[How to connect a Discord bot and Home Assistant to manage a connected home]]></description>
        <link>https://brunosabot.dev/posts/2021/a-discord-bot-for-home-assistant/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2021/a-discord-bot-for-home-assistant/</guid>
        <pubDate>Fri, 05 Nov 2021 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# A bit of&#160;context


I&#8217;ve been using [Home Assistant](https://www.home-assistant.io/) for a while to manage my connected home. Home Assistant or _Hass_ is a Python based application I have installed on a Raspberry Pi and which is able to connect. to many services from _Google Assistant_, _Philips Hue_, _The Zigbee Protocol_, and many others -1857 at the time I&#8217;m writing this post-.


The software gaves us a perfect way to organise our homes, but I needed a powerful system that can allow me to get notified and request the instance from outside my home. Basically, the usages are&#160;:

- Getting notified I left my home without setting my alarm up, then being able to activated the alarm
- Asking a room temperature to activate or deactivate the heater
- Turning on and off lights
- Knowing when it is time to get the trash out for the morning collect

There are many ways to do this with Home Assistant, but I needed something that gets my attention quickly: when working on my laptop, I barely look at my phone. When I&#8217;m out, I generally don&#8217;t have access to my laptop. Moreover, I need something that is capable to notify all my family when something has to be done, especially when it is as important as setting the home security alarm.


I made many tests of many Home Assistant integrations, and finally the one that checked all my requirements was Discord -which I&#8217;m using for many other things-. Since some points where not trivia, here is a step by step guide to set and get data between a Discord instance and a Home Assistant instance.


# The Software and requirements


First of all, I need a Home Assistant instance, which in my case is installed on a [Raspberry Pi](https://www.raspberrypi.org/). I will not explain in details how to install it right here since the tutorial on the website are good enough. Also, I think you should play around with your instance before considering connecting it with Discord. To make an installation on your own, you can head to [the documentation](https://www.home-assistant.io/installation/).


# Creation of a Discord&#160;Bot


To create a bot, you need to go to the [Discord developer dashboard](https://discord.com/developers/applications) and click on the _New Application_ button to create your bot. Choose a name, _Home Assistant_ for example, and click _Create_ .


![1_0Zz1E4924H7u7ChJsANZqg.png](https://storage.googleapis.com/brunosabot.dev/img/1_0Zz1E4924H7u7ChJsANZqg.png)


Use the purple &#8220;New Application&#8221; button


![1_ac-MpDUvhoJE4JUXsGhDrg.png](https://storage.googleapis.com/brunosabot.dev/img/1_ac-MpDUvhoJE4JUXsGhDrg.png)


Add the application name and click&#160;Create


In the application screen that follows next, feel free to change the name, the application icon or to fill the application description, then head to the _OAuth2_ menu on the left.


In this menu, you need to get and save for later your App&#8217;s _client ID_ . This ID will be needed later to activate the bot on your server.


![1_i-vohVPcDmImtYijOAMtmg.png](https://storage.googleapis.com/brunosabot.dev/img/1_i-vohVPcDmImtYijOAMtmg.png)


Gather your client ID for later&#160;usage


Now you have your Client ID, we can head to the Bot menu to make the final configuration step of our application.


Once you are on the page, just click on the _Add bot_ button and confirm the bot creation after having carefully read the consequences on the confirmation popup.


![1_ot9Na96nJhxfBgbF_V7mtg.png](https://storage.googleapis.com/brunosabot.dev/img/1_ot9Na96nJhxfBgbF_V7mtg.png)


Click the left &#8220;Bot&#8221; menu, then click the &#8220;Add bot&#8221;&#160;button


In the newly created bot, you will be able to customise its name and its picture, but the most important part is to gather the bot token and keep it somewhere. You should so far have saved two things&#160;: the application _Client ID_ and this _Token_.


Keep in mind that this token needs to be absolutely secret. **Anyone that get yours will be able to collect and send messages on behalf of your bot.**


![1_eaUdfCIte-eC07lYp4gsGg.png](https://storage.googleapis.com/brunosabot.dev/img/1_eaUdfCIte-eC07lYp4gsGg.png)


Click on &#8220;Copy&#8221; to get your bot token that will be used later, in Node Red


The final step is to activate your bot on your server. You only need to head to the following URL. Don&#8217;t forget to change the placeholder value with your previously recovered Client ID&#160;:


```plain text
https://discord.com/oauth2/authorize?scope=bot&#38;permissions=0&#38;client_id=REPLACE_WITH_YOUR_CLIENT_ID
```


Once the validation window checked the pop will jump on your server, and you will officially be done with this part.


![1_zSDdv_ZHnTsKIwii6uYJoQ.png](https://storage.googleapis.com/brunosabot.dev/img/1_zSDdv_ZHnTsKIwii6uYJoQ.png)


Select a server then Authorize it and your bot will jump&#160;in.


# The flow&#160;creation


The next part of the configuration will take place in the Node Red module on Home Assistant. Once again, I will consider now that you know the software enough to understand the specific notions I might use.


Now that I&#8217;m considering you are correctly set up, we are going to install a required module: [Node-Red](https://nodered.org/. To do so, go to the _Supervisor_ menu, choose the _Add-ons_ tab, search _node-red_ and install it. I will take few minutes before you can head up to the add-on _configuration_ tab.


![1_IWDMy6N7TjWtb2v5CpR_hQ.png](https://storage.googleapis.com/brunosabot.dev/img/1_IWDMy6N7TjWtb2v5CpR_hQ.png)


The Node RED add-on on Home Assistant


In the configuration tab, set at least a _credential_secret_ to protec your data, then get back to info and start the add-on. I personally choose to activate the menu UI, so I can access it directly from the left menu, and the watchdog but it is a choice up to you.


After few seconds, the add-on is ready and so we are! Click on the UI link, in the menu or in the _info_ tab from the add-on to get to Node-RED.


Node RED come with a lot of integrated plugins. However, the Discord plugin is not installed by default. You need to use the _Settings_ menu to activate it.


Plugins are located in the Palette tab. Search for _node-red-contrib-discord_ and install it. Some other plugins might work, but it will be up to you to test them.


![1_HxNwyHJQW4Uhbz0i4lw2fw.png](https://storage.googleapis.com/brunosabot.dev/img/1_HxNwyHJQW4Uhbz0i4lw2fw.png)


The Setting&#160;menu


![1_FpsD0o0m2nXZD2qlkqck0Q.png](https://storage.googleapis.com/brunosabot.dev/img/1_FpsD0o0m2nXZD2qlkqck0Q.png)


The node-red-contrib-discord plugin


## The base&#160;workflow


![1_fSFOZLyZ_VdpOexxRnOTIQ.png](https://storage.googleapis.com/brunosabot.dev/img/1_fSFOZLyZ_VdpOexxRnOTIQ.png)


The base&#160;workflow


The base workflow is made with four blocks&#160;:

- **discordMessage**: listen to messages coming from Discord. You should use wisely in which channels your Bot is, since it will listen for everything. This block is configurable with a Discord token, the one you saved earlier in the Discord configuration. I recommend you add a specific role for your bot, and add him in very specific channels.
- **switch**: controls the user rights. I basically use it to check if the sending user have the proper role. To do so, I make sure the _msg.memberRoleNames_ contains my Discord role. This is very useful because I have a lot of people on my server, and I of course don&#8217;t want any of them to be able to interact with my home or get sensitive data.
- **change**: update and parse my data to be handled somewhere else. I chose to make my commands on a 3 words basis (namespace, command, value). To do so, the change allows me to change the payload with a parsed value. It is pretty basic, but if you need something smarter, you can use the _function_ block where you can write plain JavaScript. **switch**: get the namespace from the payload and head to the requested flow. It might contains a lot of different outputs. I only got four for now (light, temperature, humidity and help)

You can check the config I set in every of these blocks below


![1_QlWM5kd5ZRgCnDTb1icf-Q.png](https://storage.googleapis.com/brunosabot.dev/img/1_QlWM5kd5ZRgCnDTb1icf-Q.png)


The discordMessage block configuration. Use the pencil button to add


![1__FKVFxxt6H__hnat79Gusw.png](https://storage.googleapis.com/brunosabot.dev/img/1__FKVFxxt6H__hnat79Gusw.png)


The switch block configuration. Stewjon -Star Wars fan will


![1_VcJMpHBJFAgq1xXaLICXQw.png](https://storage.googleapis.com/brunosabot.dev/img/1_VcJMpHBJFAgq1xXaLICXQw.png)


Lowercase, trim and split the playload for my use&#160;cases


![1_Oo1xwQ32t6utbJ8rV1NE9g.png](https://storage.googleapis.com/brunosabot.dev/img/1_Oo1xwQ32t6utbJ8rV1NE9g.png)


Switch to the right flow depending on the requested namespace


## First flow: Calling a&#160;service


The first scheme I have made is something that allows me to turn on and off lights directly from Discord. It is made of three basic steps.


![1_Wd9WY2Hoa9F91TwsTIwNgw.png](https://storage.googleapis.com/brunosabot.dev/img/1_Wd9WY2Hoa9F91TwsTIwNgw.png)


The light management on&#160;Discord

- **change**: This first block is aimed to make a translation between a word and Home Assistant entity. I need to transform a sentence lik _lumi&#232;re bureau on_  into a call to the proper service. Basically, it means converting _lumi&#232;re_ to _light (namespace), bureau_ to _light.lumieres_du_bureau_ (entity) and _on_ into _turn_on_ (some words are in french to be easier to understand for my family). Like I suggest on the base workflow, you might want to switch this block to a _function_ block to write plain JavaScript, which I will do someday.
- **template**: This second block convert a Node Red data into a proper Home Assistant Data, which is an object composed with three keys: _domain_, _service_ and _data_
- **call service**: This is the block that link our Node Red workflow with Home Assistant and make the proper call.

![1_x-SPnVHz2XlXBHBUQ0iGLA.png](https://storage.googleapis.com/brunosabot.dev/img/1_x-SPnVHz2XlXBHBUQ0iGLA.png)


First scheme first block: changing Discord data into a Node


![1_WHWMrOssUNdDka2NGeMmmg.png](https://storage.googleapis.com/brunosabot.dev/img/1_WHWMrOssUNdDka2NGeMmmg.png)


Second block: Formating the Node Red workflow for a Discord


![1_KOuniuzjolNRAtAInYpjXA.png](https://storage.googleapis.com/brunosabot.dev/img/1_KOuniuzjolNRAtAInYpjXA.png)


Last block: Calling the Home Assistant service


## Second scheme: Getting a sensor&#160;data


I also want to be able to recover informations from a sensor. I have currently implemented two of them, temperature and humidity.


I will only present the temperature since the behaviour will be exactly the same for humidity, just the &#8220;Get entity value&#8221; content will slightly differ.


![1_am7lKaocgozwgeu2bfsonA.png](https://storage.googleapis.com/brunosabot.dev/img/1_am7lKaocgozwgeu2bfsonA.png)


Temperature recovery from&#160;Discord

- **change**: Same as the previous scheme, allow to convert a word to the proper entity name.
- **current state**: This allow Node Red to recover data directly from Home Assistant then continue the flow with the value provided
- **template**: Template is this time used to format the data to be sent in Discord.
- **discordSendMessage**: Making the actual call to Discord. A new message from the bot will appear in the proper channel.

![1_ON8jVlKrZlrxYQjG3QphUg.png](https://storage.googleapis.com/brunosabot.dev/img/1_ON8jVlKrZlrxYQjG3QphUg.png)


First scheme first block: changing Discord data into a Node


![1_cLTTMkHgc0qnqEtJaQ_gnw.png](https://storage.googleapis.com/brunosabot.dev/img/1_cLTTMkHgc0qnqEtJaQ_gnw.png)


Recovering data from Home Assistant


![1_Bk1fE5NPJFe4IKVCwJg5dQ.png](https://storage.googleapis.com/brunosabot.dev/img/1_Bk1fE5NPJFe4IKVCwJg5dQ.png)


Simple message formatting


![1_XNF2UJfyRrFaFFuPCiJquA.png](https://storage.googleapis.com/brunosabot.dev/img/1_XNF2UJfyRrFaFFuPCiJquA.png)


Sending the message in the same channel we got the incoming&#160;message


![1_3zQiTWObeD0wS-7PP8A8dQ.png](https://storage.googleapis.com/brunosabot.dev/img/1_3zQiTWObeD0wS-7PP8A8dQ.png)


Result shown in&#160;Discord


## Third scheme: A CLI-like&#160;help


Finally, I want to be able to show an help message to whoever can use Home Assistant.


The system is just one big switch we are going to see together.


![1_e2tmco9dyZweKepD1K3N4w.png](https://storage.googleapis.com/brunosabot.dev/img/1_e2tmco9dyZweKepD1K3N4w.png)


CLI-like help&#160;workflow


To do so, we are going to need only three types of nodes:

- **switch**: This node will make the choice of the right template depending on the help type&#160;: light, humidity, temperate, and, if there is no type given, the default help.
- **template**: There are a bunch of templates. Each one is just plain text corresponding to the help I want to show on screen
- *discordSendMessage: Sending the message to the Discord server so it may looks like the bot is answering me.

![1_oIc9sryQvGaX0tfxt_CQqg.png](https://storage.googleapis.com/brunosabot.dev/img/1_oIc9sryQvGaX0tfxt_CQqg.png)


Switching to a default help or a specific&#160;one


![1_gxReqCUM7iA2sraTHGxLVw.png](https://storage.googleapis.com/brunosabot.dev/img/1_gxReqCUM7iA2sraTHGxLVw.png)


An help template&#160;example


![1_MueLiDyLRzPTSEqItj9GhQ.png](https://storage.googleapis.com/brunosabot.dev/img/1_MueLiDyLRzPTSEqItj9GhQ.png)


Finally sending the message to&#160;Discord


# Conclusion


Home assistant is a very powerful tool and combined with Node Red, it can be absolutely amazing.


The examples I have presented here are very simple on purpose, but it can be enhanced to take your Home Assistant to an higher level. I might share other posts later to explain some other workflow I had implemented.


I hope you&#8217;ll get closer to the perfect home automation thanks to this tutorial]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[My Journey From React to React Native]]></title>
        <description><![CDATA[The things that changed for me switching when from web applications to native applications]]></description>
        <link>https://brunosabot.dev/posts/2020/my-journey-from-react-to-react-native/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2020/my-journey-from-react-to-react-native/</guid>
        <pubDate>Mon, 21 Sep 2020 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[I&#8217;ve recently started working on an Android application, and as a React developer, I made the easy choice to use and test [React Native](https://reactnative.dev/) to do so because it helped me stay in my comfort zone and also gives me the opportunity to explore iOS someday.


Even if it is the same framework, using React for native applications is a little bit different than React on the web.


I&#8217;m writing this article to share the main differences I have found between the two platforms along with a few tips I had to figure out to obtain the desired final behavior.


# View or Text&#8202;&#8212;&#8202;There Is No&#160;div


When working on a web application, we mostly use `div` and `span` tags for many usages. Since we are not on the web, this is no longer a possibility.


Instead, the content is made with `View` and `Text` that we could associate with the two tags above, but they have some additional constraints.


## The View&#160;element


With the `View` element, you can&#8217;t add anything else inside other than components. That means it cannot contain text, which the `Text` component is for. As an unfortunate consequence, it has a larger tree in your application, but it helps to separate concerns between layout and text.


`gist:brunosabot/90c2f7acc44752bbbfabadeee3fe5ae2`


Hello world component in React Native


Based on the previous point, you can easily figure out that you can&#8217;t apply text-related styles to a `View` component. The text styles like `color` or `fontSize` need to be applied to the `Text` component.


`gist:brunosabot/091ade8b1c4ba353700e75462f0573ca`


Layout styles on View, text styles on Text


`View` is also a _flexbox_ container that can only support two display values: `none` or `flex`. It can change numerous things if you are not confident with the model, but it is much more powerful than the classic block model used by default on the DOM.


You can learn more about this layout system on [CSS-Tricks](https://css-tricks.com/snippets/css/a-guide-to-flexbox/). Every flex property is supported in React Native, from `align-items` to `flex-grow`.


There is, however, one major difference between the web version and the native version: the default value of `flex-direction`. If we have `row` on the web, it is set to `column` in React Native. Basically, this means that elements are placed by default from top to bottom instead of left to right.


`gist:brunosabot/b80ccc2e6eb3122ee8a4a32b2b789b00`


Flexbox usage on React Native


Finally, `View` is not clickable. If you need a click behavior on it, you&#8217;ll have to wrap it into a `Touchable*` component:

- `TouchableHighlight` to add a background color on click.
- `TouchableOpacity` to reduce opacity on click.
- `TouchableWithoutFeedback` to have no feedback on click, which I don&#8217;t recommend for user experience reasons.
- `TouchableNativeFeedback` (only on Android) to have the ripple effect on the button.

`gist:brunosabot/dcd186a2e30979c880e90783194c5460`


Example usage of TouchableHighlight


## The Text&#160;element


If we can easily compare the `Text` element to a `span` tag on the web, the difference is as noticeable as with views.


The `Text` element&#8202;&#8212;&#8202;as it is aptly named&#8202;&#8212;&#8202;exists only to make the rendering of text contents. We cannot use it for any layout-related stuff we might need. Therefore, `display: "flex"` will have no effect. Neither will `position`.


However, the `Text` inherits styles from the parent `Text` component like it does on the web.


`gist:brunosabot/568c0e8f551cb5b4510cf13b30e04e7c`


Text component style inheritance


Like `View`, the `Text` component is not clickable. If that&#8217;s a behavior you need, you will have to wrap into one of the `Touchable*` components.


Finally, `Text` is only meant to contain text and other `Text` components. You should not include layout-related components like `View`, `ScrollView`, or `FlatList`.


# Replace Input With TextInput


Since the Native API is not DOM, we do not have `input` elements either, but React provides a component for the times when we need a form.


The `InputField` component works the same as `input` but also has a `onChangeText` attribute that accepts a callback with the value as an argument. No more need for `event.target.value`!


`gist:brunosabot/1203807b99ff7f1741254fbe2f67e8d1`


TextInput and the onChangeText callback


# The CSS&#160;Usage


If I&#8217;m using [CSS Modules](https://github.com/css-modules/css-modules) when I&#8217;m working on a web application, it is a bit different on native, where the CSS usage is more the CSS-in-JS way. The stylesheets are created with the `StyleSheet.create` method that is provided by React Native and is a key/value object of class/styles for the component.


`gist:brunosabot/e9aa6cf67dccfacf71c2989efb01f448`


Styling with StyleSheet.create()


If there are units in CSS, there are not in React Native&#8202;&#8212;&#8202;or more precisely, units are always set in `dp`, so the render will be right even if the phones do not all have the same pixel ratio. It makes the CSS usage a bit different, but if you want to make things simpler, just consider them pixels.


If we used to have shortcuts in CSS, it is not the same in React Native: `padding` must be a number and not a list of values in a string, `backgroundColor` is used for the color, and so on.


To illustrate that rule, assume that the CSS `padding: "8 16"` is not valid, and so `background: "#333333"`.


Even if these are a bit longer to type in, I find it way more explicit than the shortcuts we are used to. Plus, they are always complicated to understand for a beginner.


After a couple of hours, I had definitely adopted this new way of writing CSS.


`gist:brunosabot/76181843b33d7e256bb6dc86cfbec5cf`


dp units and shortcuts


# Scalable Vector&#160;Graphics


If SVG is used a lot on the web, it is not natively supported in React Native. We need to use it with an external package: `react-native-svg`.


However, the package is made to be used exactly like on the web with just a little difference: the first uppercase character.


`gist:brunosabot/5d97361874d62efd324e63565671d0f6`


Simple SVG in React Native


# Overflow


If you want a scrollable `View`, you need to switch to the `ScrollView` component. It acts the same but has a scrollbar built in.


Since the component has a vertical scroll by default, you can use the `horizontal` attribute to make it scroll on the _x_-axis.


For performance reasons, you can also use the`FlatList` component, which is a bit more complicated to use, but it will make your long lists scroll fast. If it is something you need, I encourage you to [look at the official documentation](https://reactnative.dev/docs/flatlist).


# Tips and&#160;Tricks


## Touchable components are applied to a single&#160;element


If you get the error `Error: React.Children.only expected to receive a single React element child`, then you just need to wrap your elements in a new `View` component.


It seems pretty obvious what to do, but it can be a bit disturbing when coming from the web: When using `Touchable*` components, you need to have a single layout item.


## Line breaks in&#160;`Text`


On the web, new lines are made with `&#60;br /&#62;`, but since native is not DOM, you can simply use `{"\n"}` in your `Text` components or directly in a string (e.g.&#160;`&#60;Text&#62;{"Hello\nworld!"}&#60;/Text&#62;`).


## Views in&#160;Text


You cannot have `View` elements in `Text` elements. This throws the following error: `Cannot add a child that doesn't have a YogaNode to a parent without a measure function!`.


It might make your tree a bit more complex with some code duplication, but you should always find a way to avoid this message.


# Conclusion


Even though React Native is based on React, there are plenty of differences. On one hand, we use React on the web and use the DOM API. On the other hand, we use the native layouts for Android, iOS, and others. But it is still very easy to get into. Do not hesitate to give it a try!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[An Opinionated Way to Structure React Apps]]></title>
        <description><![CDATA[Based on my experience acquired building several big projects]]></description>
        <link>https://brunosabot.dev/posts/2020/an-opinionated-way-to-structure-react-apps/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2020/an-opinionated-way-to-structure-react-apps/</guid>
        <pubDate>Tue, 05 May 2020 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[When we first develop a React app, we can just put every component in a folder and it works. But when it comes to larger projects, it might be difficult to find our way between files if we keep using React this way.


So how can we handle a bigger project? [Dan Abramov has a way](https://react-file-structure.surge.sh/). You don&#8217;t think this is very helpful? Actually, it is. It&#8217;s the best way to find the perfect architecture that will fit your needs, but at a cost of many iterations in folder creation and removal.


Today, I&#8217;m introducing the result of my many moves, making a base structure for people seeking a way to improve their own.


# Initial Considerations


Before we start, I&#8217;d like to point out that I&#8217;m presenting an opinionated way to structure an app. In some projects, I had to do things differently because the app&#8217;s core concept was too different. It might also be the case for you.


Also, there are several ideas I&#8217;d like to introduce so you will better understand the why.


First of all, I use [atomic design](https://atomicdesign.bradfrost.com/). Some components are only visual. Basically, it concerns every component that will end up in my [Storybook](https://storybook.js.org/). I call them ui components. Atomic design also brings template components.


Some other components are given a specific behavior to a form field, like an enhanced form field that gives a validation pattern to a browser default form field. They are the organisms within atomic design.


Finally, I&#8217;m using the React Context API instead of redux, as I [explained in one of my previous posts](https://medium.com/@brunosabot/how-i-dropped-redux-for-the-context-api-7338d481e179). I create top-level components that I call providers.


# Getting Started With the Root&#160;Folder


Working with [create-react-app](https://create-react-app.dev/), the root folder of my application is the `src` folder in which I place several folders:

- `App`&#8202;&#8212;&#8202;The folder where the main component is placed containing global providers and main routing.
- `components`&#8202;&#8212;&#8202;Where every React component of the application belongs.
- `gql`&#8202;&#8212;&#8202;In which I can find every piece of a GraphQL request I can make in my application.
- `libs`&#8202;&#8212;&#8202;This is a bit of a mess, but it contains everything else. It is generally composed of fewer than ten files, so I never had to split them better.

This is the better ratio I found between simplicity and code splitting for the base structure. Since React is a component framework, you can easily imagine that the `components` folder will be a bit more complex.


I will not explain in detail the three other folders. You can have a look at the sample tree at the bottom of this post to find out more about the kind of files placed in there.


# The Components Folder


Here we are: the main part of the application. This one is composed of many more subfolders. Keep in mind that if you copy this structure, you do not need to absolutely use them all if it doesn&#8217;t make sense in your project. For example, the `ui` folder doesn&#8217;t make sense in a [Material-UI](https://material-ui.com/) application.

- `hooks`&#8212;&#8202;Where I place a good amount of the hooks I use in my app. I have a lot of them to embrace the power of reusability, so I also create subfolders to illustrate the job they belong to. For example, I often have a `useInterval` hook to handle cyclic jobs. I also place in there a `useUser` hook that gives me the current connected user information.
- `modals`&#8202;&#8212;&#8202;This regroups every modal in my project. I used to place them elsewhere, but I actually found that I often use them many times in the application, and they are quite numerous. By having their own folder, it became simpler for me to work on them.
- `organisms`&#8202;&#8212;&#8202;The folder where I place the functional components I spoke about earlier. It can be split into subfolders if there are too many of them, which happens a lot.
- `providers`&#8202;&#8212;&#8202;Components that contain global data or feature logic. To find out more about what a provider looks like, I invite you to take a look [at a previous post](https://medium.com/better-programming/how-i-dropped-redux-for-the-context-api-7338d481e179) where I replace redux with them.
- `svg`&#8202;&#8212;&#8202;The home of every icon used in the application since create-react-app [can include them natively](https://create-react-app.dev/docs/adding-images-fonts-and-files/#adding-svgs). You might have a designer, but in case you don&#8217;t, I really love the [Material Design Iconset](https://materialdesignicons.com/), where I can always find the perfect icon for my apps.
- `templates`&#8202;&#8212;&#8202;In which I have the page layouts of my atomic design application. It&#8217;s not the richest folder of the app, but taking into consideration what the layouts are for, they are better isolated.
- `ui`&#8202;&#8212;&#8202;Where the atoms and molecules of my application are. This is one of the heaviest folders in the application, so it is split up by domain subfolders.
- `pages`&#8202;&#8212;&#8202;This corresponds to the pages defined in my application. This is the most complex folder because it is recursive. We&#8217;ll talk about it in a specific chapter right after this one.

This is a lot of folders, right? The most difficult part of my perfect folder structure was to keep it simple ([KISS!](https://en.wikipedia.org/wiki/KISS_principle)), but without mixing apples and oranges. This is why I placed atoms and molecules of atomic design in the same folder, but I also often have domain subfolders.


# The Pages Subfolder


Before coming to the folder structure, let&#8217;s talk about URLs. I found that cutting every URL in my app in two sections of the path (the domain and the page) is the simpler and more robust way to build the page path.


I might also have additional parameters to show a specific detail page. These ones are not limited in amount.


For example, I have these pages:

- `/user/login`
- `/user/account`
- `/todo/list`
- `/todo/details/123`
- &#8230;

But I do not have these ones:

- `/user` will redirect to `/user/dashboard`, for example.
- `/` will probably also redirect to `/user/dashboard`.

These URLs give you a hint on how structured the folders will be. Without surprise, we have a first folder that is the domain and a second one that is the page.


As I mentioned earlier, the page folder is also recursive. Why? Simply because sometimes the content is not global to the app. A `useTodoList` hook is only used in the `/todo/list` page and the `TodoItem` component also.


So inside a page folder, you can also find a `components` folder with every folder defined earlier but `pages`.


# Putting It All&#160;Together


That was a lot of words to define the overall structure. But an example is often better than words, so here it is:


```plain text
src
 |- App
 | |- App.jsx
 |- components
 | |- hooks
 | | |- useInterval.jsx
 | |- modals
 | | |- AddTodoModal.jsx
 | |- organisms
 | | |- PrivateRoute.jsx
 | | |- forms
 | | | |- TextInput.jsx
 | |- pages
 | | |- todo
 | | | |- list
 | | | | |- TodoList.jsx
 | | | | |- components
 | | | | | |- hooks
 | | | | | | |- useTodoList.jsx
 | | | | | |- organisms
 | | | | | | |- TodoItem.jsx
 | | |- user
 | | | |- login
 | | | | |- UserLogin.jsx
 | |- providers
 | | |- UserProvider.jsx
 | | |- TodoProvider.jsx
 | |- svg
 | | |- check.svg
 | |- templates
 | | |- LoggedPage.jsx
 | | |- LoginPage.jsx
 | |- ui
 | | |- alert
 | | | |- Alert.jsx
 | | | |- Alert.module.css
 | | | |- Alert.stories.jsx
 | | | |- Alert.test.js
 | | |- button
 | | | |- Button.jsx
 | | | |- Button.module.css
 | | | |- Button.stories.jsx
 | | | |- Button.test.jsx
 |- gql
 | |- todo
 | | |- TodoCreate.gql
 | | |- TodoDelete.gql
 |- libs
 |- preload.js
```


Even if the example is pretty simple, it contains everything to illustrate the previous explanations.


# Conclusion


Even if this folder structure for React is a work of many years on how to organize a project, it might not suit every need. However, as of today, it fits all my projects&#8217; needs and makes me particularly efficient during my work.


If you encounter some issues of your own, I would be glad to hear from you about how this proposal is bringing you trouble. But remember, the right folder structure is not necessarily mine but the one that fits your project. After all:

&#62; &#8220;Move files around until it feels right.&#8221;&#8202; &#8212;
&#62;
&#62; [Dan&#160;Abramov](https://react-file-structure.surge.sh/)
&#62;
&#62;]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[How to Debug a React Context API App]]></title>
        <description><![CDATA[Using the Redux DevTools extension]]></description>
        <link>https://brunosabot.dev/posts/2020/how-to-debug-a-react-context-api-app/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2020/how-to-debug-a-react-context-api-app/</guid>
        <pubDate>Tue, 17 Mar 2020 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Some time ago, I shared how [I dropped Redux for the Context API](https://medium.com/better-programming/how-i-dropped-redux-for-the-context-api-7338d481e179) when I&#8217;m creating a React application. The post got some great feedback, but I also had some people saying that it&#8217;s pretty hard to debug compared to the [Redux DevTools](https://github.com/reduxjs/redux-devtools) and asking me if there is an easy method to do it.


The answer is yes. Actually, if there is something awesome about Redux, it&#8217;s the DevTools. The great part is we can link them easily with our Redux-free app&#8202;&#8212;&#8202;and with everything we like, really.


Let&#8217;s now see how it works!


# Redux DevTools&#160;API


When we have Redux DevTools installed, the extension automatically injects a special object (`__REDUX_DEVTOOLS_EXTENSION__`) in the window. A weird name for sure, but it prevents any conflicts with your existing code.


And that&#8217;s where everything starts: This object gives us everything we need&#8202;&#8212;&#8202;`connect` and `disconnect` methods that link our code with the Redux DevTools.


However, if you just try to run these functions, you will still see nothing in the DevTools because we first need to initiate the session.


To do so, we take advantage of the object that returns the `connect` method. It possesses an init method that launches the DevTools session.


Basically, it looks like this:


`gist:brunosabot/16b83292b1f9abf5e7f6e3ef7174802b`


Simple Redux DevTools connection.


Even if this works, you will still see nothing in the DevTools because the session is closed as soon as we create it.


# A DevTools&#160;Provider


To make the session permanent when you are developing your app, you will need a `Provider`, which looks like this:


`gist:brunosabot/02fd13b082f19105005818b3ef166139`


A basic Redux DevTools Provider.


If you add this piece of code at the very top of your application, you will see the startup session in the Redux DevTools that you can identify with the `@@INIT` event that pops in.


![1__1__Ee__Cxvnmx1bAgstmX3jA.png](https://storage.googleapis.com/brunosabot.dev/img/1__1__Ee__Cxvnmx1bAgstmX3jA.png)


The INIT event that pops&#160;in.


# Sending Events to the&#160;DevTools


Now that we are able to start a session, the next step is to send an event to the DevTools, which is as simple as we can imagine: The `devTools` object that we have created also provides a `send` method, which takes a name and some data to illustrate the change.


Basically, it looks like this:


`gist:brunosabot/4fbaec5625c6bd0b77ce233a0be6836c`


Simple Redux DevTools connection with a sent event.


# Putting Everything Together


We now have everything to make it work. We will just add a few things in our `Provider` to make it truly usable:

- A hook to allow simple event sending to the DevTools session.
- A Context API so the DevTools can be available in the whole project.
- A simple sending method with default name values.

`gist:brunosabot/39e8176a7aebaf36696f0841b0e7dba2`


The usage is now pretty simple in the application. We need to call the `useReduxDevtools` hook we created in the `Provider` file to wrap the initial method into a high-order function that handles the debug session:


`gist:brunosabot/44795880a4029b46d84b3255db71f740`


Look how useReduxDevtools is called on line 10.


![1__ZRD7PZ36M__dTOFaRBU6aRQ.png](https://storage.googleapis.com/brunosabot.dev/img/1__ZRD7PZ36M__dTOFaRBU6aRQ.png)


And voil&#224;, our Context API inside Redux DevTools!


Now when we are calling the `setTheme` method, it pops in the DevTools so you can inspect what is going on!


# Conclusion


It is very easy to connect any app to the Redux DevTools, and you can start debugging any app without Redux right now.


Also, let&#8217;s revisit something I said in my previous post about the Context API: It&#8217;s an easy API to use and can replace Redux in many cases, but keep in mind that Redux is much larger than the Context API. Choose wisely if you need to make the switch.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[New year resolutions the right way]]></title>
        <description><![CDATA[My experience on how to succeed them]]></description>
        <link>https://brunosabot.dev/posts/2020/new-year-resolutions-the-right-way/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2020/new-year-resolutions-the-right-way/</guid>
        <pubDate>Fri, 31 Jan 2020 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[_tl;dr: Imagine your resolution for the next year, make little steps and start them whenever you feel ready._


At the beginning of last year, I made a few resolutions with a goal: to start becoming a better myself. It&#8217;s something I have never achieved to keep during a whole year, not even a single little one of them.


This year I was able to complete some of them -not all of them still because of unfortunate events- and it feels great.


How did I achieve this? It was actually pretty simple. Just do not imagine and start them on January 1st


Is that all? Actually no. Also remember the tortoise and the hare.


# So what were the resolutions?


## Healthier life


In November 2018, I had -minor- health issues. It took me until the end of the year to picture what I can do to fix some of my lifestyle issues. By January 1st, I had two ideas&#160;:

- I can replace coffee with tea, but I need to do it right: learn what is a good tea quantity, make it at the right temperature and infuse it for the right time.
- I used to drink alcohol -few but still- everyday, and had habit to drink alone at home. So I can restrict myself to only one drink day by week, without caring about the quantity.

It was definitely not so much, but a little step forward sounds easier than a sprint. Moreover, there is no obligation to just stick to those two, so I started to run on November.


_Spoiler alert:_ it worked and it feels great. No more stomach aches thanks to the tea, no more unpleasant alcohol caused state everyday, and running gave me a better breathing.


## Writing


Another resolution was to start writing blog posts. If you are reading this, you probably guess that it is a success. But it was not that easy to start: I wasn&#8217;t ready at all and had no idea.


It took me time, writing down any subject that sounds interesting, remembering myself that every topic might be interesting exactly like my speaker experience told me&#160;: we all have something to learn.


In August, I wrote and publish my first post and the feedback was amazing. I was so happy that, even if my subject was very basic, a lot of people were actually interested and learn stuff&#8230; so I wrote 4 more that had an even better feedback.


# My two&#160;cents


For many years, I made pointless resolutions that I didn&#8217;t achieve because they were too hard or things I didn&#8217;t believe in.


Today, I&#8217;m writing down my experience hoping that some of you, readers, get inspired by my method and get resolutions done.


So I&#8217;m writing down everything I&#8217;d like to accomplish during a whole year, and I choose only resolutions that I still believe in on January 1st.


I also stay pretty vague in what I should do: if I want to be more ecological, I can simply make my own compost and it will be a first step forward.


Finally, even if I look at my resolution list every day of my life, I only start them when I&#8217;m ready, like in August and November for some of them in 2019.


Remember, **little steps**, **stay vague** and **wait to be ready**.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Publish your Cloud Run App with GitHub Actions]]></title>
        <description><![CDATA[A very fast way to deploy your application with GitHub]]></description>
        <link>https://brunosabot.dev/posts/2019/publish-your-cloud-run-app-with-github-actions/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2019/publish-your-cloud-run-app-with-github-actions/</guid>
        <pubDate>Mon, 23 Sep 2019 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Google recently announced [Cloud Run](https://cloud.google.com/run/), a new [Google Cloud Platform](https://cloud.google.com/gcp/?utm_source=google&#38;utm_medium=cpc&#38;utm_campaign=na-US-all-en-dr-bkws-all-all-trial-p-dr-1007179&#38;utm_content=text-ad-lpsitelinkCCexp2-any-DEV_c-CRE_113120493247-ADGP_Hybrid%20%7C%20AW%20SEM%20%7C%20BKWS%20%7C%20US%20%7C%20en%20%7C%20Multi%20~%20Cloud%20Platform-KWID_43700011014879364-kwd-10876442192&#38;utm_term=KW_cloud%20platform-ST_cloud%20platform&#38;gclid=CjwKCAjw2qHsBRAGEiwAMbPoDG8AYhrx-uAWd-_A5PQbzTIHw7LCFPa1E54xXPqJj3nMh1K3Wt55ChoCNG4QAvD_BwE) (GCP) feature, to deploy your [Docker](https://www.docker.com/) applications fast and easily. This guide will explain how to build and deploy a simple static application using the new continuous integration/continuous delivery system provided by GitHub: [Actions](https://github.com/features/actions).


To make this project live, we&#8217;ll use the following tools:

- Google Cloud Run, to execute our Docker containers
- [Google Cloud Container Registry](https://cloud.google.com/container-registry/) to store our Docker images
- GitHub Actions to manage the continuous deployment
- GitHub to store the source code of our project.

# Configure the GCP&#160;Project


## Service account&#160;creation


The first step is to create a _service account_ which will allow us to connect from GitHub actions. To do so, you can click the &#8220;Create Service Account&#8221; button in the &#8220;IAM &#38; Admin/Service Accounts&#8221; menu in the GCP interface. Now fill in the &#8220;Service Account Name&#8221; field with the value &#8220;GitHub-actions&#8221; and the &#8220;Service Account Description&#8221; field with the value &#8220;Account used by GitHub Actions to connect with GCP.&#8221;


At this point, keep the value generated in the field _Service account ID_. It is the service account username that will be used later. It should look like `github-actions@key-partition-000000.iam.gserviceaccount.com`.


Clicking on the &#8220;Create&#8221; button brings up the roles configuration stage. We need two roles:


Clicking on the &#8220;Create&#8221; button brings up the roles configuration stage. We need two roles:

- **Cloud Run Admin**, the role which will allow us to create a new Cloud Run deployment;
- **Storage Admin**, the role which allow us to upload our Docker images to the GCP&#8217;s Container Registry.
- **Service Account User**, the role that allows the service account to act as a user.

The &#8220;Continue&#8221; button takes us to the third and final step: JSON key creation. To do that, just click the &#8220;Create Key&#8221; button, choose &#8220;JSON&#8221; &#62; &#8220;Create.&#8221; A file will be downloaded, referred to as `key-partition-000000&#8211;0123456789ab.json`. Keep it, we&#8217;ll use it later.


## API activation


Now that we have a user with the proper permissions, we need to activate the two services we need.


To enable the container registry, choose the &#8220;Container Registry&#8221; (obvious, right?) menu of the GCP interface, and click the &#8220;Enable Container Registry API&#8221; button&#8230; and we&#8217;re done.


To enable Cloud Run, choose the &#8220;Cloud Run&#8221; (obvious again, right?) menu from the GCP interface and click &#8220;Start Using Cloud Run.&#8221; Again, we&#8217;re done.


# Project Creation


To initiate the project, let&#8217;s create a GitHub repository and add several files:

- A simple welcome `&#60;h1&#62;Hello World!&#60;/h1&#62;` HTML page;
- A simple error `&#60;h1&#62;Server error&#60;/h1&#62;` HTML page;
- A `nginx/default.conf` file that will serve as the welcome file
- A `Dockerfile` to build Docker images;
- A&#160;`.github/workflows/googlecloudrun.yml` file to configure the continuous deployment.

## `nginx` configuration


`gist:brunosabot/ed052ea7a9a97dca82294438ed913490`


Cloud Run listens on `localhost:8080`. So, the first step is to set these values with `server_name` and `listen`.


Then, we configure the file location that `nginx` should serve; the `root` key is where all the static files to serve are located. In this example, that&#8217;s in `usr/share/nginx/html`. The `index` key declares to `nginx` what `root` files to resolve; here, `index.html` or `index.htm` will be resolved.


Finally, we forward the 50X errors to a special file with the command `error_page` and handle the mapping in `nginx`with `location` to our error HTML file.


## Dockerfile


`gist:brunosabot/e0fef5aa5865b457bd26f22051c287c8`


Since our application is really simple, we only have three steps in our Docker file:

- We choose the `alpine nginx` image to ensure we have the lightest bundle possible;
- We copy the `nginx conf` in its target folder;
- Finally, we copy our HTML files in the `public` folder of our newly-created container.

## GitHub actions&#160;YAML


`gist:brunosabot/a28b8dd9c7090b66a98427fe56d26047`


This is probably the most complicated part of the project, although you&#8217;ll see that it&#8217;s still pretty simple.


Our project will make the deploy on every commit made on the master branch. We will, however, ignore everything made on the other branches since it&#8217;s a deployment script. If you need to go further, you may want to make a deploy on a different endpoint for every other branch, but that&#8217;s not today&#8217;s topic.


Our CD also has only one job, since we have a pretty simple application -simple `nginx` server with static files. But in real life, you may want other steps to build your application, or to run the tests, for example. We choose to run the build on `ubuntu-latest`, but [other possibilities are available](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idruns-on).


The job will be composed of several steps. But before digging into them, let&#8217;s see what actions are required:

- `actions/checkout@v1` to pull the GitHub project sources we are going to deploy;
- `actions/gcloud/cli@master` to perform every step related to GCP.

Now let&#8217;s dive into the steps. There are six of them:


The first one is to ensure you get the application sources from GitHub. Because Actions might be triggered on issue creation, for example, in which we probably don&#8217;t need to get the sources. GitHub gives only the mandatory data by default.


The second one uses the Docker to build our image. Moreover, since we are uploading the image to Google Cloud Container Registry, we have to tag it with the name of the target image URI. The URI is built using:

- A host: `eu.gcr.io`;
- A project: `secrets.GCLOUD_PROJECT`, a _secret_ we will talk about in a moment;
- An app name: `secrets.GCLOUD_APP_NAME`; also a _secret._

The third step is the beginning of GCP-related commands. We first need to authenticate ourselves with the secret key `GCLOUD_AUTH` via the `auth` action.


The fourth step links GCP and Docker together so it knows where to push images.


The fifth step consists of uploading our image on the container registry. This one has a specificity: we need to use both Docker and GCloud in that command, so choose the GCloud action, and use the `sh` entrypoint in order to have both the`gcloud` and `docker` commands available, to push the image. Thanks to the fourth step, Docker will push images in the right place.


The final step is actually two commands. Here, the result of the first is not available in the second if we split them into two different steps. The two commands are responsible for:

- Installing the beta components, since Google Cloud Run is still in beta;
- Deploying the app into Cloud Run. You will need to set the [region](https://cloud.google.com/sdk/gcloud/reference/beta/run/deploy#--region) you prefer and the [platform](https://cloud.google.com/sdk/gcloud/reference/beta/run/deploy#--platform) (managed or [GKE](https://cloud.google.com/kubernetes-engine/)).

## Declaring GitHub&#160;secrets


![1__CLhcjP8t9IfEEtwJ1fwH8Q.png](https://storage.googleapis.com/brunosabot.dev/img/1__CLhcjP8t9IfEEtwJ1fwH8Q.png)


Secrets interface for GitHub&#160;Actions


In our YAML file, we reference three secret keys:

- `GCLOUD_APP_NAME`: the name of the application on Cloud Run. In this example, it will be `cloud-run-github-actions`.
- `GCLOUD_PROJECT`: the project ID on Google Cloud Platform. You can find it on the &#8220;Project Selection&#8221; popup.
- `GCLOUD_EMAIL`&#160;: the service account email we create earlier that looks like `github-actions@key-partition-000000.iam.gserviceaccount.com`.
- `GCLOUD_AUTH`: the base 64-encoded content of the JSON file we downloaded at the beginning of the story, `key-partition-000000&#8211;0123456789ab.json`.

And we&#8217;re ready to execute our deployment from GitHub Actions!


# Conclusion


We can now deploy our project continuously and easily on Google Cloud Run. Our example is pretty simple, but since we&#8217;re using a Docker image, we&#8217;re free to have any kind of application we want. Just note that the application must listen to the `PORT` environment variable: Cloud Run will only listen to an app on that port!


You can find the sources on [Github](https://github.com/brunosabot/cloud-run-github-actions).]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Deploy Your ZEIT Now App With GitHub Actions]]></title>
        <description><![CDATA[Implement custom logic without having to create an app to perform a task]]></description>
        <link>https://brunosabot.dev/posts/2019/deploy-your-zeit-now-app-with-github-actions/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2019/deploy-your-zeit-now-app-with-github-actions/</guid>
        <pubDate>Mon, 02 Sep 2019 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[I recently got access to [GitHub Actions](https://github.com/features/actions) and I decided to test it with a simple deployment in [ZEIT Now](https://zeit.co/home). My first steps were to look at [this wonderful post](https://medium.com/peerigon/how-to-continuously-deploy-a-cra-using-github-actions-and-zeit-f7bbd3b60da3) from [Leonhard Melzer](https://medium.com/@leomelzer).


It contains a lot of useful information but, unfortunately, it uses the old workflow syntax, which is now deprecated in favor of [YAML](https://yaml.org/). I spent a lot of time making the conversion, so here is an post on how I achieved it with a `yml` file.


## Requirements

- At the time I&#8217;m writing this story, GitHub Actions is still in beta. [You can ask for access here](https://github.com/features/actions) and wait for your request to be accepted.
- You will also need an account on [ZEIT](https://zeit.co/home).
- Of course, you will need a GitHub account and a repository to store your app sources.
- Last but not least, an application to deploy.

# Get Started


As I mainly work with React, I will give you an example with a `create-react-app` application but feel free to use any other library you like.


# ZEIT Now Configuration


Now requires a configuration file at the root of the repository, named `now.json`, which contain the app configuration on the hosted environment.


`gist:brunosabot/ac51677500a9f4b019245c9e9c21e6f1`


Sample now configuration file


Let&#8217;s consider the important parts of this file:

- `version`: The configuration file version. You can simply use 2 as version 1 is deprecated.
- `name`: The name of our application used on the ZEIT dashboard. It will create a custom subdomain on your application.
- `builds`: How the application can be built. As I&#8217;m deploying a `create-react-app`, I use `@now/static-build` but there are [many other options you might want to consider](https://zeit.co/docs/v2/advanced/builders).
- `regions`: Where we want the application to be deployed. I chose `bru` to use GCP in Brussels, but you can choose your favorite one [on that list](https://zeit.co/docs/v2/network/regions-and-providers).
- `routes`: Simply the mapping of where any incoming request should point. Note that everything unknown will be redirected to the `index.html` file.

There is more! As the `@now/static-build` I use has some constraints, I need to respect them:

- We need to add a `now-build` script in the `package.json` file that will be run by the builder&#8217;s entry-point.
- The output data should be included in a `dist` folder, while `create-react-app` sets the content in a `build` folder.

Putting everything together, our `package.json` file will look like this:


`gist:brunosabot/f5fd154e3c9190d732a974ddc1e3b289`


To have a lighter gist, I only kept the name, version, and scripts keys


Okay, everything is now set for deployment. Our next step is to implement the appropriate GitHub Actions.


# GitHub Actions Configuration


To create a new deployment workflow, we need to create a&#160;`.github` folder at the root of our repository. You might already be using it if you use the [issue templates](https://help.github.com/en/articles/creating-issue-templates-for-your-repository).


Then, add a `workflows` subfolder that will contain as many workflows as you want.


Next, we will create a YAML file that corresponds to the deploy workflow we want. You can create many files to create multiple workflows, one for the tests and one to deploy, as an example.


`gist:brunosabot/e1b8f12917df3c95a700a30d3a061b28`


Now, let&#8217;s analyze what we wrote in the file:

- `name`: A pretty name for the workflow which we will see in GitHub.
- `on`: When the workflow should be executed. [There are a lot of events we can pick](https://help.github.com/en/articles/events-that-trigger-workflows).
- `jobs`: The list of jobs to execute.

![1__AQRdaWkdiS5gB4uakwD6fg.png](https://storage.googleapis.com/brunosabot.dev/img/1__AQRdaWkdiS5gB4uakwD6fg.png)


The jobs list in the GitHub interface


The jobs look almost the same. I will only present the _publish_ one that contains more information than the others.

- `name`: Once again, a pretty name to identify the current job.
- `runs-on`: The operating system that will execute the job. There are few possibilities [listed here](https://help.github.com/en/articles/workflow-syntax-for-github-actions#jobsjob_idruns-on).
- `needs`: The jobs required before executing this one. GitHub Actions are smart; they will follow the `needs` order so you don&#8217;t have to worry about how you have your jobs sorted. You will see that illustrated in the screenshot below.
- `steps`: The list of actions to execute

Once again, let&#8217;s dive in to see how steps are configured:

- `name`: Guess what? A pretty name for the action!
- `uses`: Finally, the action to execute. There are plenty of actions available. The two I use are `actions/checkout@v1` to get my repository files and `actions/zeit-not@master` for the ZEIT-Now related actions. The anatomy of the action is to have the repository first (e.g.&#160;actions/checkout), then the branch we want to use (e.g.&#160;master).
- `env`: The environment variables stored within the GitHub interface.
- `with`: Allows the configuration of actions with some variables. In this example, we just want to add the `-prod` args to the `now` command that will be executed.

![1__Tb____ZlqkA42Z6dtscs1cew.png](https://storage.googleapis.com/brunosabot.dev/img/1__Tb____ZlqkA42Z6dtscs1cew.png)


With the jobs on the left and the steps on the&#160;right


## Storing secrets


In the YAML file, we use a `ZEIT_TOKEN` secret. You can set it in the _Settings_ menu, where we can find a _Secrets_ menu. Once you add a secret, you will not be able to get the value again.


![1__Ix__D9z26HYO0D8Qtt3V99g.png](https://storage.googleapis.com/brunosabot.dev/img/1__Ix__D9z26HYO0D8Qtt3V99g.png)


# Conclusion


Setting up GitHub Actions was a bit complicated at first, especially because the stories you can find on the internet use the old syntax. But, in the end, everything is pretty simple and functional for a beta feature.


There is, of course, more to say on the usage, considering the build performance, for example. But that will be the content of another story. Stay tuned!


I enjoyed exploring the API and I hope you will be as excited as I am to start digging into the Actions!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[A Complete Pre-Commit Workflow]]></title>
        <description><![CDATA[Using Husky to make an awesome git workflow]]></description>
        <link>https://brunosabot.dev/posts/2019/a-complete-pre-commit-workflow/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2019/a-complete-pre-commit-workflow/</guid>
        <pubDate>Wed, 21 Aug 2019 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# What Is This All&#160;About?


I really like making perfect commits, and I particularly hate appending or creating a new commit because of a guideline mistake. So I needed a tool that would bark at me when I was about to make a bad commit.


The Internet is full of tutorials explaining [what](https://githooks.com/) [Git](https://git-scm.com/book/uz/v2/Customizing-Git-Git-Hooks) [hooks](https://www.digitalocean.com/community/tutorials/how-to-use-git-hooks-to-automate-development-and-deployment-tasks) [are](https://hackernoon.com/automate-your-workflow-with-git-hooks-fef5d9b2a58c) [for](https://medium.com/the-andela-way/git-hooks-beautifully-automate-tasks-stages-bfb29f42fea1), but I still had to spend a lot of time to build a nice workflow for my JavaScript applications to use them correctly. So here is an overview of my toolkit that I use today on every single project.


The first library focused on workflow automation is [Husky](https://github.com/typicode/husky)&#8202;&#8212;&#8202;because a dog barking at me gets my attention. Husky is a JavaScript library that makes Git hooks easier. It offers the possibility of integrating them directly into our JavaScript projects, saving us from having to deal with startup guidelines or startup scripts on repository initialization.


Using Husky is really simple. We just need to add a new `husky` key into the `package.json` file. This new entry contains a key/value object, `hooks` which represents our Git hooks, and the script we want to execute:


![1__JUYFGIOYiHTMvgPzLE72ig.png](https://storage.googleapis.com/brunosabot.dev/img/1__JUYFGIOYiHTMvgPzLE72ig.png)


Here is a very simple


```plain text
package.json
```


file with the husky hooks. For simplicity sake and because the husky version might change, it didn&#8217;t include the


```plain text
devDependencies
```


.


# Clarifying Our&#160;Needs


When I&#8217;m working on a React application, I use several tools to keep a consistent code and simplify the teamwork:

- [Prettier](https://prettier.io/), on JS, JSX, JSON, CSS and MD files, to format my code into something consistent across developers.
- [Eslint](https://eslint.org/), with the [Airbnb config](https://github.com/airbnb/javascript/tree/master/packages/eslint-config-airbnb), to propose a robust linting on my applications. I, however, prioritize Prettier&#8217;s rules with `[eslint-config-prettier](https://github.com/prettier/eslint-config-prettier)`.
- [Jest](https://jestjs.io/), for my applications unit tests.
- [Stylelint](https://stylelint.io/), to keep my CSS modules clean.

Unfortunately, Husky only supports executing one command at a time per type of Git hook.


I will also have to check every file modified to apply the proper tools, which is tedious work.


Thankfully, there is already an awesome tool to help me do that: [lint-staged](https://github.com/okonet/lint-staged).


lint-staged is awesome because it executes the needed scripts only on modified files, which make the hooks run very fast. Thanks to lint-staged, we are now able to run a very fast workflow on the delta commit stage, therefore drastically reducing the usual duration of all scripts.


Same as Husky, lint-staged is a new key/value object in our `package.json` file that represents the scripts to execute.


![1__FsOI8QuZx28kau15BLGUkQ.png](https://storage.googleapis.com/brunosabot.dev/img/1__FsOI8QuZx28kau15BLGUkQ.png)


Same as before, I have only included the interesting part of my package.json file


Now, everything just looks so simple. I just need to tell Husky to use lint-staged and tell lint-staged what I need to do on my files.


# The Final&#160;Result


Putting it all together, this is what my `package.json` file looks like:


`gist:brunosabot/4eac55bd532a55a8f02f372050ba840e`


Same as before, I have only included the interesting part of my package.json file


When I make a commit, my hooks do the following:

- For my **CSS** files, run Prettier then run Stylelint then add the updates automatically into the Git staging area.
- For my **JS** and **JSX** files, find and execute tests related to my changes, then run Prettier, followed by Eslint, and finally add the updates automatically to the Git staging area.
- For my **MD** and **JSON** files, run Prettier and add the updates automatically to the Git staging area.

And here we are, not messing with our commits anymore. 💪


There are a lot of other checks we can do before any commits and also other Git hooks that we could use to improve the versioning system workflow, I&#8217;d like to hear your needs and I&#8217;d also like to see you making improvements on my workflow!


![1__tohTAyB2gOTzOl8mmHm36Q.jpeg](https://storage.googleapis.com/brunosabot.dev/img/1__tohTAyB2gOTzOl8mmHm36Q.jpeg)


Ascending to more robust development workflow. Photo credit to&#160;me.


# Bonus


While writing this post, I realized that my boilerplate application is based on a lot of additional scripts. So here is a listing of what I add to every single new project of mine. Note this example is applied to a React application and I use `yarn` as a package manager.


So, first install the required packages:


```plain text
yarn add --dev cross-env enzyme enzyme-adapter-react-16 eslint-config-airbnb eslint-config-prettier eslint-plugin-import eslint-plugin-jsx-a11y eslint-plugin-react eslint-plugin-react-hooks husky jest-enzyme lint-staged prettier stylelint stylelint-config-css-modules stylelint-config-prettier stylelint-config-recommended stylelint-order stylelint-prettier
```


Then, add the config files:


`gist:brunosabot/30ba8f8fc2ff8bf1a75d8f973b300578`


And we are good to go!]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Learning expedition Zenika: Ce qu'en pensent les devs]]></title>
        <description><![CDATA[La Silicon Valley, c’est le rêve américain de beaucoup de développeurs. Zenika y est allé.]]></description>
        <link>https://brunosabot.dev/posts/2019/learning-expedition-zenika-ce-qu-en-pensent-les-devs/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2019/learning-expedition-zenika-ce-qu-en-pensent-les-devs/</guid>
        <pubDate>Thu, 25 Jul 2019 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[La Silicon Valley, c&#8217;est le r&#234;ve am&#233;ricain de tout d&#233;veloppeur qui s&#8217;int&#233;resse de pr&#232;s &#224; tout ce qui se fait de nouveau dans le monde du high-tech, c&#8217;est l&#8217;endroit ou l&#8217;on retrouve les plus grands groupes tels que Google, Apple, Facebook, Amazon, Microsoft, Netflix, Mozilla, Pivotal, etc..


Gr&#226;ce &#224; la Learning Expedition de [Zenika](https://zenika.com/), nous avons pu y passer une semaine et nous rendre compte par nous-m&#234;mes de la mani&#232;re dont se passent les choses dans cette partie de la Californie.


![2019-07-25-zle19-1.jpg](https://storage.googleapis.com/brunosabot.dev/img/2019-07-25-zle19-1.jpg)


# Les inconv&#233;nients


Il y a deux choses qui reviennent tr&#232;s souvent dans de nombreuses entreprises de la Silicon Valley qui semblent, dans un premier temps, de superbes ajouts &#224; la culture d&#8217;entreprise.


## Profitez chez nous de vacances illimit&#233;es.


Si dans un premier temps c&#8217;est quelque chose qui nous semble une aubaine, la r&#233;alit&#233; est tout autre : avec des vacances illimit&#233;es, vous ne penserez plus, lorsque arrivera leur &#233;ch&#233;ance, &#224; poser en express tous les jours que vous n&#8217;aurez pas pris. Et des diff&#233;rentes interviews que nous avons pu faire lors de notre semaine, c&#8217;est un pari gagnant pour les entreprises puisque les employ&#233;s ont tendance &#224; prendre moins de jours qu&#8217;avec une quantit&#233; d&#233;finie de jours par an. Cela s&#8217;explique de deux mani&#232;res.

- Tout d&#8217;abord par une certaine pression sociale o&#249; il faut mener &#224; bien ses projets et cela passe par la pr&#233;sence au bureau.
- Aussi, il nous a &#233;t&#233; remont&#233; qu&#8217;il n&#8217;&#233;tait pas toujours simple de r&#233;ussir &#224; faire valider ses cong&#233;s.

En plus de cela, les jours de cong&#233;s non pris lors d&#8217;un d&#233;part ne sont pas pay&#233;s, car il n&#8217;y a pas de jours pr&#233;d&#233;finis ce qui baisse encore le co&#251;t des cong&#233;s.


Pour autant, les quelques Fran&#231;ais que nous avons crois&#233;s d&#233;rogent un peu &#224; cette r&#232;gle, par habitude de nos 5 semaines et pour les p&#233;riodes d&#8217;acclimatation lorsqu&#8217;ils retournent en France voir leurs familles et leurs amis.


![2019-07-25-zle19-2.jpeg](https://storage.googleapis.com/brunosabot.dev/img/2019-07-25-zle19-2.jpeg)


## Le repas du midi vous sera offert.


Pour les plus gourmands d&#8217;entre nous, nous aurions vite fait de nous ruer sur la nourriture, et c&#8217;est clairement ce que recherchent les entreprises : en apportant le repas directement dans les locaux, les employ&#233;s passent moins de temps &#224; manger et hop, dix minutes plus tard ils sont de retour au travail. Ce qui est bien plus rentable que de laisser un peu plus d&#8217;une heure &#224; trouver un endroit o&#249; manger &#224; proximit&#233; des bureaux. &#201;videmment, l&#8217;alternative est aussi d&#8217;avoir une cantine pour les plus grosses entreprises pour industrialiser le service du repas.


En revanche, toutes les entreprises, ou presque, poss&#232;dent des boissons et de quoi grignoter en libre-service.


# Am&#233;lioration des conditions de travail


Si nous avons pu voir que certaines situations sont des solutions cach&#233;es pour faire travailler davantage, il existe quelques astuces que nous avons pu rencontrer qui ont pour but d&#8217;am&#233;liorer le quotidien des employ&#233;s. Bien entendu, les entreprises sont toujours gagnantes de ce point de vue puisqu&#8217;un employ&#233; heureux sera moins absent et plus assidu &#224; la t&#226;che.


## Candide feedback.


On a tous personnellement v&#233;cu la situation o&#249; quelqu&#8217;un de notre entourage ou nous-m&#234;me faisons un reproche sur quelqu&#8217;un d&#8217;absent. Netflix a choisi d&#8217;instaurer le Candide feedback qui se compose de deux actions simples. Tout d&#8217;abord, dans cette situation, les tiers doivent et ont pris l&#8217;habitude d&#8217;arr&#234;ter le plaignant pour lui demander &#8220;est-ce que tu es all&#233; lui en parler ?&#8221; afin d&#8217;&#233;viter la propagation de cette ambiance de ragots &#224; la machine &#224; caf&#233;. Ensuite, chaque ann&#233;e, chaque membre de Netflix va &#233;crire un rapport pour un autre coll&#232;gue de son choix, sans consid&#233;ration hi&#233;rarchique, qui est cens&#233; vanter ses qualit&#233;s et ses d&#233;fauts. Le coll&#232;gue qui le re&#231;oit peut donc choisir de confirmer ses points forts, peut ainsi conna&#238;tre ses points faibles et &#233;ventuellement travailler dessus s&#8217;il en a envie et la capacit&#233;. Gr&#226;ce au cadre qu&#8217;offre le Candide feedback, les employ&#233;s peuvent donc proposer des axes d&#8217;am&#233;lioration sans crainte &#224; leurs sup&#233;rieurs.


## La zone de confort.


Certaines personnes sont tr&#232;s fortes techniquement, mais le sont tr&#232;s peu socialement. D&#8217;autres sont dans le cas inverse. Chez Google, ce n&#8217;est pas un probl&#232;me : tout est pens&#233; en interne pour faire travailler les personnes dans leur zone de confort. Il existe ainsi une multitude de domaines avec des &#233;chelons permettant de justifier l&#8217;expertise d&#8217;un employ&#233; dans un domaine. On peut donc, &#224; un moment de sa carri&#232;re, se retrouver &#224; un &#233;chelon tr&#232;s &#233;lev&#233; d&#8217;un domaine tr&#232;s technique, avec la paie associ&#233;e, sans que l&#8217;on ne demande des t&#226;ches manag&#233;riales comme on le constate souvent en France.


## 20% time.


Pour encourager la productivit&#233; et l&#8217;imagination &#224; la base de Gmail ou Google maps, Google a propos&#233; par le pass&#233; &#224; ses employ&#233;s de passer 20% de leur temps sur un projet de leur choix profitant &#224; l&#8217;entreprise. Ce 20% de temps n&#8217;existe plus, mais il constitue tout de m&#234;me l&#8217;ADN de Google qui est particuli&#232;rement permissif et laisse les &#233;quipes s&#8217;organiser. La personne qui aura une id&#233;e de projet pourra constituer sa team, mais avec un objectif : le projet devra avoir un million d&#8217;utilisateurs en un an sinon il sera ferm&#233;.


## Solutions de bien-&#234;tre au travail.


Il est parfois difficile de pouvoir profiter apr&#232;s le travail de certaines activit&#233;s d&#233;tente ou sportives. On retrouve chez Google de nombreuses installations pour pouvoir en profiter sur les lieux du travail, comme un lieu o&#249; se situent deux terrains de beach volley. Chez LinkedIn, il a &#233;t&#233; choisi de faire intervenir des professionnels pour des massages par exemple, directement sur les lieux du travail. Les frais restent n&#233;anmoins &#224; la charge de l&#8217;employ&#233;.


## Poste de travail.


L&#8217;endroit o&#249; l&#8217;on va passer une grande partie de notre temps lorsque l&#8217;on est au travail, c&#8217;est notre bureau. Une constante observ&#233;e lors de toutes nos visites est l&#8217;omnipr&#233;sence des bureaux assis-debout. Aussi, au-del&#224; du traditionnel open-space que l&#8217;on a majoritairement rencontr&#233;, nous avons pu observer chez Netflix une initiative interm&#233;diaire entre l&#8217;open-space et les bureaux ferm&#233;s : des &#238;lots individuels en forme de U. Ils sont compos&#233;s d&#8217;un bureau principal parall&#232;le au couloir, d&#8217;un bureau secondaire et d&#8217;un grand tableau blanc sur la 3&#232;me partie du U. Cette disposition permet de s&#8217;adapter aux diff&#233;rentes situations : travail en solo quand il faut avancer ses sujets, accueil de coll&#232;gues avec le bureau secondaire, et enfin la pr&#233;sence du tableau pour les phases de brainstorming.


## Pre-mortem, yet, disagree &#38; commit.


Slack a d&#233;cid&#233; de mettre en place trois concepts pour am&#233;liorer leur fonctionnement. Tout d&#8217;abord, un projet commence par un pre-mortem, qui consiste &#224; faire une r&#233;union initiale de lancement o&#249; l&#8217;&#233;quipe va d&#233;finir tout ce qui pourrait mal se passer afin de mieux voir venir les probl&#232;mes et &#234;tre moins impact&#233; par les &#233;checs. Ensuite, leurs r&#233;unions sont parfois dynamis&#233;es par un disagree &#38; commit, o&#249;, lorsque des gens d&#8217;une m&#234;me &#233;quipe ne sont pas d&#8217;accord, l&#8217;un d&#233;cide de conclure la r&#233;union en acceptant la solution de l&#8217;autre et clos la conversation en interdisant de rouvrir la discussion ; de cette mani&#232;re, Slack &#233;vite les r&#233;unions aux d&#233;bats st&#233;riles. Enfin, afin d&#8217;encourager les &#233;quipes &#224; &#234;tre positives, ils ajoutent le seul mot yet lorsque les collaborateurs font part de leur manque de connaissances, pour les encourager &#224; d&#233;velopper leur curiosit&#233; (I don&#8217;t know how to develop a Java application YET).


![2019-07-25-zle19-3.jpg](https://storage.googleapis.com/brunosabot.dev/img/2019-07-25-zle19-3.jpg)


# Les petits plus


## Snack et soda en libre-service.


Impossible d&#8217;affirmer qu&#8217;il s&#8217;agit de quelque chose de strictement propre &#224; la Silicon Valley, mais toutes les entreprises poss&#232;dent des distributeurs de nourriture et de boissons en libre-service et &#224; volont&#233;.


## Culture d&#8217;entreprise forte et surveill&#233;e.


Chaque entreprise est particuli&#232;rement fi&#232;re des actions prises pour leur culture d&#8217;entreprise et leurs employ&#233;s et s&#8217;assure que celle-ci reste intacte ou que les &#233;volutions sont p&#233;rennes et coh&#233;rentes. Netflix va m&#234;me jusqu&#8217;&#224; privil&#233;gier la culture fit au recrutement &#224; la comp&#233;tence technique.


## Libert&#233; d&#8217;innover pour l&#8217;entreprise.


Les employ&#233;s sont libres et encourag&#233;s &#224; proposer le n&#233;cessaire &#224; l&#8217;am&#233;lioration de l&#8217;entreprise, que ce soit technique ou au sujet de la culture. Parfois n&#233;anmoins, les innovations sont tr&#232;s similaires dans diff&#233;rentes sections de l&#8217;entreprise et m&#232;nent &#224; un creative chaos : savez-vous combien Google poss&#232;de d&#8217;apps de messagerie ? Chez Netflix par exemple, chaque &#233;quipe est autonome pour d&#233;velopper ses propres outils internes m&#234;me si une autre &#233;quipe d&#233;veloppe quelque chose de similaire ; on pr&#233;f&#232;re dupliquer que d&#8217;avoir un &#8220;dead lock&#8221; organisationnel.


## Bienveillance par d&#233;faut.


Certains diront qu&#8217;il manque de la sinc&#233;rit&#233;, mais, sans doute aid&#233;s par le principe du tip qui oblige les serveurs &#224; &#234;tre agr&#233;ables avec les clients, les encouragements sont r&#233;guliers : faire son travail est d&#233;j&#224; une raison largement suffisante pour recevoir des f&#233;licitations.


## L&#8217;&#233;volution des salles de r&#233;union.


Dans les locaux de Netflix, on peut trouver des salles de r&#233;union en cercle sur plusieurs &#233;tages d&#8217;une forme assez similaire aux amphith&#233;&#226;tres romains dans le but de favoriser le &#8220;face to face&#8221; et les &#233;changes.


## Les grabinettes.


Chez Netflix, les grabinettes sont des armoires en libre-service o&#249; l&#8217;on peut r&#233;cup&#233;rer gratuitement du petit mat&#233;riel tel que des adaptateurs, des chargeurs, des cartes m&#233;moires, des alimentations, des souris et bien d&#8217;autres. Le prix de chacun de ces objets est tout de m&#234;me affich&#233; afin de responsabiliser les employ&#233;s au co&#251;t des produits et donc au co&#251;t pour Netflix.


## Coins d&#233;tente.


Chaque entreprise poss&#232;de une zone o&#249; les employ&#233;s peuvent se d&#233;tendre : de la salle de sieste aux canap&#233;s, du baby foot &#224; la salle de diffusion de s&#233;ries, toutes ont leurs sp&#233;cificit&#233;s qui donneront un moyen de rel&#226;cher la pression.


![2019-07-25-zle19-4.jpg](https://storage.googleapis.com/brunosabot.dev/img/2019-07-25-zle19-4.jpg)


# Conclusion


Les entreprises de la Silicon Valley sont nombreuses et toutes &#224; la recherche de talent. De ce constat, toutes cherchent &#224; innover pour attirer et conserver leurs employ&#233;s le plus longtemps possible. Si certains exc&#232;s subsistent encore dans de nombreuses entreprises, la Silicon Valley a bien compris que le bien-&#234;tre de leurs employ&#233;s &#233;tait le meilleur moyen de leur garantir une certaine stabilit&#233;.


Article co-ecrit avec Alexandre Garnier, Antoine Carpier-Jouan, Martin Piegay, Nicolas Lassalle, Yoann Prot et Valentin Cocaud.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[How I dropped Redux for the Context API]]></title>
        <description><![CDATA[React 16 introduced a new Context API to replace the deprecated one… Here is my way to replace Redux with this new API.]]></description>
        <link>https://brunosabot.dev/posts/2019/how-i-dropped-redux-for-the-context-api/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2019/how-i-dropped-redux-for-the-context-api/</guid>
        <pubDate>Thu, 25 Jul 2019 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[React 16 introduced a new Context API to replace the deprecated one. OK, it&#8217;s been more than a year since the release of version 16.3, but it still seems fresh in the React ecosystem.


This new API came with the promise to solve a lot of problems with the previous experimental way to use contexts. To me, it did a lot more; it changed the way I make React applications. This is the story of how I managed it.


I won&#8217;t give a course on how Redux works. If you want a refresher, you can check [the amazing course from Dan Abramov on Egghead](https://egghead.io/courses/getting-started-with-redux). Plus, you&#8217;ll eventually remove Redux from your apps, so do we need a full course on it?


There are a few requirements to understand the code: I will use [React hooks](https://en.reactjs.org/docs/hooks-intro.html) and [React fragments](https://en.reactjs.org/docs/fragments.html) in the short form `&#60;&#62;`.


OK, let&#8217;s say we have an app that tells if I&#8217;m available for a beer. It consists of the following:


`gist:brunosabot/31b74e415c0709fbd48120aac58885d1`


How to have a beer status in React with Redux.


In my sample code, I created four files to handle the parts of a Redux application:

- `actions/beer.js`: A file that contains a constant for every action in my app. This could be inlined directly in the other files, but I like to keep things clear and concerns separated.
- `dispatchers/beer.js`: The home of every action my Redux model has. In this case, I only have one `toogleBeerAvailability` method, which dispatches the action from the previous file.
- `reducers/beer.js`: The storage engine of my Redux model, which changes the value of my availability if the `TOGGLE_AVAILABILITY_FOR_BEER` dispatcher is called.
- `components/beer.jsx`: The component that shows and toggles my availability. We use `react-redux` to map the redux properties to my component props.

That&#8217;s a lot of code, but it&#8217;s necessary for a robust system with Redux. Now, we&#8217;re going to drop Redux with the same result. But first, why do we want to drop Redux?


I made that move simply to reduce weight in my application by removing two dependencies: `redux` and `react-redux`. I&#8217;m also not a big fan of having multiple dependencies in my applications, so I&#8217;m jumping on the possibility to remove two of them.


So here&#8217;s how it works. Keep in mind that it may not be a perfect solution or even a recommended one, but it&#8217;s the one I use in my projects and works. But let&#8217;s stop chatting and dive into the code.


I&#8217;m working with a single state file I call _Provider_. It contains everything to handle the state. In this first sample, it&#8217;s just a getter and a setter I receive from a state hook.


`gist:brunosabot/3152a94a3ae10ab9eb2816844a4ede89`


How to have a beer status with the Context API


This looks much simpler and more efficient, but there are still a few issues to improve it:

- The getters and setters are in the same object, which is a bit of a mess.
- The `toggleAvailability` method is managed in the children component, which is not functional.
- We will probably encounter performance issues due to our state change.

For the first one, I like to cut the object into two sub-objects, `actions` and `values`&#160;, like dispatchers and states in Redux. It eventually looks like this:


`gist:brunosabot/06304e257e0a76c3a78ca634c12f8dc4`


How to have a beer status with the Context API and a bit of structure


For the second one, we just need to move the call into the parent component and add the action in our new actions section. It will make our `Beer` component a lot simpler.


`gist:brunosabot/f279be98e49c76c53c02398590747248`


How to have a beer status with the Context API, structure, and consistency


As for performance, we still have two issues in our component:

- The `toggleAvailability` method will be re-evaluated every time the `Provider` component is updated
- The value object which contains the state will also be updated every time the `Provider` component has a change.

Fortunately, React provides two hooks to handle a cache of our data.


We will first encapsulate the `toggleAvailability` method in the `useCallback` hook. It will ensure the returned method will always be the same when the data in the second parameter has not changed. This will be possible because React&#8217;s `useState` hook guaranteed its set method would be the same despite the renders.


Then we&#8217;ll use the `useMemo` hook to encapsulate the `value` object. This hook is almost the same as `useCallback` but for objects. It will also get a second parameter to show what data it depends on.


`gist:brunosabot/ae9c659645e08cce2549f64a59838d62`


And that&#8217;s all, folks! We no longer have Redux in our application and have a clean Context usage. I hope you give the Context API a try!


![1__ucBcf14roFs8__c4Nc9Dp9Q.jpeg](https://storage.googleapis.com/brunosabot.dev/img/1__ucBcf14roFs8__c4Nc9Dp9Q.jpeg)


A new way for my React projects. Photo credit to&#160;me.


# **References**


[Hooks API Reference - React](https://en.reactjs.org/docs/hooks-reference.html)[How to use React Context effectively](https://kentcdodds.com/blog/how-to-use-react-context-effectively)]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Des webapps style Android ICS]]></title>
        <description><![CDATA[Intégration d'un application pour suivre le style Android ICS]]></description>
        <link>https://brunosabot.dev/posts/2013/des-webapps-suivant-le-modele-android-ics/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2013/des-webapps-suivant-le-modele-android-ics/</guid>
        <pubDate>Thu, 11 Jul 2013 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[En parall&#232;le de la mise en place d&#8217;une maquette de webapp pour mes projets personnels, j&#8217;ai r&#233;cemement d&#233;velopp&#233; un d&#233;but de maquette pour permettre de d&#233;velopper des webapps &#224; la mani&#232;re Android ICS (4.0 &#8211; Ice Cream Sandwich).


A l&#8217;heure actuelle, la maquette ne propose qu&#8217;un nombre limit&#233; de fonctionnalit&#233;s &#224; savoir :

- Header android ICS Bleu
- Menu lat&#233;ral
- Options de l&#8217;application
- Blocs &#224; la Google Now
- L&#8217;objectif &#233;tant &#224; terme d&#8217;arriver &#224; proposer une maquette beaucoup plus compl&#232;te, avec notamment
- ~~Indicateur de chargement~~ Fait
- ~~Header dans les autres couleurs ICS~~ Fait
- ~~Onglets~~ Fait
- Gestion des formulaires
- Toute suggestion pertinente qui sera propos&#233;e.

Le projet est partag&#233; en open source sur [Github](https://github.com/brunosabot/html-android), n&#8217;h&#233;sitez pas &#224; ajouter votre pierre &#224; l&#8217;&#233;difice et r&#233;cup&#233;rer les sources pour vos propres d&#233;veloppements !


Pour un premier aper&#231;u des fonctionnalit&#233;s, voici quelques captures d&#8217;&#233;cran :


![app_default.png](https://storage.googleapis.com/brunosabot.dev/img/app_default.png)


Vue par d&#233;faut de l&#8217;application


![app_menu.png](https://storage.googleapis.com/brunosabot.dev/img/app_menu.png)


Affiche du menu


![app_options.png](https://storage.googleapis.com/brunosabot.dev/img/app_options.png)


Menu option d&#233;roulant]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[La « dirty transparency »]]></title>
        <description><![CDATA[Sur un PNG-24, avec de la semi-transparence, on se retrouve confronté à un gros surplus d'informations, appelé « dirty transparency ».]]></description>
        <link>https://brunosabot.dev/posts/2013/optimisation-des-images-comprendre-la-dirty-transparency/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2013/optimisation-des-images-comprendre-la-dirty-transparency/</guid>
        <pubDate>Tue, 02 Jul 2013 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Les consignes webperf un peu partout vous recommanderont de r&#233;duire la taille de vos images. Pour cela, il suffit d&#8217;utiliser des logiciels appropri&#233;s afin de supprimer toutes les m&#233;ta-donn&#233;es qui pourrait alourdir le composant graphique.


Lorsque l&#8217;on travaille sur une image de type PNG-24, avec de la semi-transparence, on se retrouve confront&#233; &#224; un gros surplus d&#8217;informations caus&#233;s par la transparence, appel&#233; &#171; dirty transparency &#187; par les anglophones.


Avant d&#8217;expliquer ce dont il s&#8217;agit, il faut revoir la structure du format PNG : Le PNG-24 est compos&#233; de trois informations de couleur : Rouge-Vert-Bleu, avec chacune de ces informations cod&#233; sur&#8230; 24/3 = 8 bits, soit 256 (28) teintes diff&#233;rentes.


En fait, le fonctionnement est identique aux couleurs hexad&#233;cimales que l&#8217;on utilise en CSS par exemple, mais r&#233;p&#233;t&#233; pour chaque point.

- Premier point : R-255, V-255, B-255
- Deuxi&#232;me point : R-253, V-254, B-255
- Troisi&#232;me point : R-251, V-253, B-255
- etc.

Une fois l&#8217;image trait&#233;e dans sa totalit&#233;, le PNG est compress&#233; afin d&#8217;obtenir une image de plus petite taille.


Jusque l&#224;, la seule optimisation possible de la taille de l&#8217;image r&#233;sulte de l&#8217;utilisation de diff&#233;rentes options pour que la compression des donn&#233;es brutes soit optimale.


Lorsque l&#8217;on induit la transparence, On ajoute en une information de plus : le niveau de transparence, sur 8 bits soit 256 (28) niveaux de transparence.

- Premier point : R-255, V-255, B-255, A-30
- Deuxi&#232;me point : R-253, V-254, B-255, A-0
- Troisi&#232;me point : R-251, V-253, B-255, A-0
- etc.

Quelle est la diff&#233;rence ? Seulement le canal &#8220;Alpha&#8221; qui donne l&#8217;information du niveau de transparence pour le pixel. L&#8217;image continue d&#8217;exister avec toutes les informations de couleur, m&#234;me si le pixel est d&#233;clar&#233; &#8220;totalement transparent&#8221;, et, si toutes ces informations sont diff&#233;rentes comme dans l&#8217;exemple montr&#233; ci-dessous, la compression ne sera jamais optimale.


Heureusement, ce probl&#232;me du format PNG est facile &#224; contourner : Il suffit de remplacer tout les pixels transparents (Soit alpha = 0) par une m&#234;me couleur, totalement transparente :

- Premier point : R-255, V-255, B-255, A-30
- Deuxi&#232;me point : R-0, V-0, B-0, A-0
- Troisi&#232;me point : R-0, V-0, B-0, A-0
- etc.

Ainsi, la compression de l&#8217;image sera bien meilleure, car le pattern le pattern &#8220;pixel transparent&#8221; est le m&#234;me dans toute l&#8217;image. J&#8217;ai personnellement r&#233;ussi &#224; diviser la taille de l&#8217;image jusqu&#8217;&#224; 3.


Pour arriver &#224; ceci, j&#8217;utilise le code php suivant, que je vous recommande d&#8217;utiliser une seule fois, &#224; la mise en production par exemple, puisqu&#8217;il est assez co&#251;teux en ressources :


```php
$i = '/path/to/png/file';$o = '/path/to/output/file';$in = imagecreatefrompng($i);imagealphablending($in, false);imagesavealpha($in, true);$x = imagesx($in);$y = imagesy($in);$mx = 0;while ($mx &#60; $x) {    $my = 0;    while ($my &#60; $y) {        $c = imagecolorat($in, $mx, $my);        if (($c &#38; 0x7F000000) == 0x7F000000) {            imagesetpixel($in, $mx, $my, 0x7F000000);        }        $my++;    }    $mx++;}imagepng($in, $o, 9);
```


Une fois le script pass&#233;, vous pouvez/devez aussi passer l&#8217;image dans votre compresseur classique afin de gagner encore quelques octets sur la taille de vos images.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Déplacer un commit de l'historique GIT]]></title>
        <description><![CDATA[Couper coller des commits d'une branche à une autre]]></description>
        <link>https://brunosabot.dev/posts/2013/deplacer-un-commit-de-l-historique-git/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2013/deplacer-un-commit-de-l-historique-git/</guid>
        <pubDate>Thu, 20 Jun 2013 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Il m&#8217;est arriv&#233; r&#233;cemment de vouloir d&#233;placer un commit de mon historique sur une autre branche : &#224; la base, il ne s&#8217;agissait que d&#8217;un petit correctif, qui s&#8217;est transform&#233; en une fonctionnalit&#233; &#224; part.


J&#8217;ai en revanche pass&#233; beaucoup de temps &#224; trouver comment faire pour effectuer cette modification dans mon d&#233;pot. Vous trouverez ci-dessous la liste de commandes que j&#8217;ai utilis&#233; afin d&#8217;arriver &#224; mes fins.


Attention, si vous avez d&#233;j&#224; pouss&#233; les modifications sur un autre d&#233;p&#244;t, vous risquez d&#8217;avoir des probl&#232;mes. J&#8217;utilise par ailleurs le raccourci `git lg` que vous pourrez retrouver dans le d&#233;pot [dotfiles](https://github.com/brunosabot/dotfiles) de mon [github](https://github.com/brunosabot/).


```plain text
[09:53] bruno@PC-Bruno:~/git/project[master]$ git lg
* 034bcdf &#8211; (HEAD, master) Update on new functionality (Bruno Sabot 10 minutes ago)
* d39bc5b &#8211; New functionality (Bruno Sabot 10 minutes ago)
* 4102855 &#8211; Small bugfix (Bruno Sabot 10 minutes ago)
* 768cd9a &#8211; Initial commit (Bruno Sabot 10 minutes ago)
[09:55] bruno@PC-Bruno:~/git/project[master]$ git checkout 4102855
Note: checking out &#8216;4102855&#8217;.
You are in &#8216;detached HEAD&#8217; state. You can look around, make experimental
changes and commit them, and you can discard any commits you make in this
state without impacting any branches by performing another checkout.
If you want to create a new branch to retain commits you create, you may
do so (now or later) by using -b with the checkout command again. Example:
git checkout -b new_branch_name
HEAD is now at 4102855&#8230; Small bugfix
[09:56] bruno@PC-Bruno:~/git/project[(4102855&#8230;)]$ git branch project
[09:56] bruno@PC-Bruno:~/git/project[(4102855&#8230;)]$ git checkout master
Previous HEAD position was 4102855&#8230; Small bugfix
Switched to branch &#8216;master&#8217;
[09:56] bruno@PC-Bruno:~/git/project[master]$ git lg
* 034bcdf &#8211; (HEAD, master) Update on new functionality (Bruno Sabot 12 minutes ago)
* d39bc5b &#8211; New functionality (Bruno Sabot 12 minutes ago)
* 4102855 &#8211; (project) Small bugfix (Bruno Sabot 12 minutes ago)
* 768cd9a &#8211; Initial commit (Bruno Sabot 13 minutes ago)
[09:56] bruno@PC-Bruno:~/git/project[master]$ git rebase &#8211;onto 768cd9a 4102855 master
First, rewinding head to replay your work on top of it&#8230;
Applying: New functionality
Applying: Update on new functionality
[09:56] bruno@PC-Bruno:~/git/project[master]$ git lg
* 1aa8de7 &#8211; (HEAD, master) Update on new functionality (Bruno Sabot 4 seconds ago)
* 4afb8d3 &#8211; New functionality (Bruno Sabot 4 seconds ago)
* 768cd9a &#8211; Initial commit (Bruno Sabot 13 minutes ago)
[09:56] bruno@PC-Bruno:~/git/project[master]$ git checkout project
Switched to branch &#8216;project&#8217;
[09:56] bruno@PC-Bruno:~/git/project[project]$ git lg
* 4102855 &#8211; (HEAD, project) Small bugfix (Bruno Sabot 13 minutes ago)
* 768cd9a &#8211; Initial commit (Bruno Sabot 13 minutes ago)
[09:56] bruno@PC-Bruno:~/git/project[project]$ git checkout master
Switched to branch &#8216;master&#8217;
[09:56] bruno@PC-Bruno:~/git/project[master]$ git merge project
Merge made by recursive.
0 files changed, 0 insertions(+), 0 deletions(-)
create mode 100644 project2
[09:56] bruno@PC-Bruno:~/git/project[master]$ git lg
* 66b91dd &#8211; (HEAD, master) Merge branch &#8216;project&#8217; (Bruno Sabot 1 seconds ago)
|
| * 4102855 &#8211; (project) Small bugfix (Bruno Sabot 13 minutes ago)
* | 1aa8de7 &#8211; Update on new functionality (Bruno Sabot 25 seconds ago)
* | 4afb8d3 &#8211; New functionality (Bruno Sabot 25 seconds ago)
|/
* 768cd9a &#8211; Initial commit (Bruno Sabot 13 minutes ago)
```


Toute la magie s&#8217;op&#232;re &#224; la commande `git rebase &#8211;onto 768cd9a 4102855 master` :

- Le **rebase** signifie que l&#8217;on veut d&#233;placer des commits de notre arbre
- **&#8211;onto 768cd9a** indique que les commits d&#233;plac&#233;s le seront &#224; la suite du commit #768cd9a
- **4102855 master** s&#233;lectionne tous les commits entre 4102855 (exclu) et master (inclus)

A vous de jouer !]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Comprendre la minification CSS]]></title>
        <description><![CDATA[Comment fonctionne la minification réellement et pourquoi est-ce un vrai gain ?]]></description>
        <link>https://brunosabot.dev/posts/2011/comprendre-la-minification-des-css/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/comprendre-la-minification-des-css/</guid>
        <pubDate>Wed, 29 Jun 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Minifier son CSS a bien entendu pour avantage de r&#233;duire le nombre de caract&#232;res pr&#233;sents dans une feuille de style, et donc son poids.


Il existe plusieurs types de compression. La plus simple consiste &#224; supprimer les caract&#232;res superflus du CSS. Par exemple :

- Suppression des blancs multiples
- Suppression des retour &#224; la ligne
- Replacement des 0px par 0
- Suppression des &#187; ou &#8216; dans une URL (ex. background:url(toto.jpg))

Il est n&#233;anmoins possible d&#8217;aller plus loin dans nos optimisations. En quoi &#231;a consiste ?


# Regrouper les m&#234;mes r&#232;gles


```css
h1 {  font-size: 2em;}h2 {  font-size: 1.5em;}h1 {  color: #369;}
```


Ici, on peut simplifier le code (de 9 octets) en


```css
h1 {  font-size: 2em;  color: #369;}h2 {  font-size: 1.5em;}
```


Sans compter les r&#233;p&#233;titions &#233;ventuelles de certains attributs


# R&#233;&#233;crire les r&#232;gles CSS


```css
h1 {  margin-top: 10px;  margin-bottom: 20px;}
```


Dans cet exemple, regrouper les attributs peut faire gagner 17 octets :


```css
h1 {  margin: 10px 0 20px;}
```


De m&#234;me que pour le moyen pr&#233;c&#233;dent, les r&#233;p&#233;titions de certains attributs peuvent encore plus diminuer la taille de la feuille de style.


# D&#233;composer les CSS


```css
h1 {  font-size: 2em;  color: #369;}h2 {  font-size: 1.5em;  color: #369;}h3 {  font-size: 1.2em;  color: #369;}
```


En d&#233;composant les r&#232;gles on gagne 7 octets :


```css
h1 {  font-size: 2em;}h2 {  font-size: 1.5em;}h3 {  font-size: 1.2em;}h1,h2,h3 {  color: #369;}
```


Certes le gain est moins important, mais report&#233; sur une feuille de style compl&#232;te, il devient int&#233;ressant


# Que faire ?


Effectuer ces modifications peuvent casser la mise en page du site, du fait de la gestion des priorit&#233;s de CSS. Il faut donc savoir le faire avec pr&#233;caution.


Il existe bien entendu des outils pour g&#233;rer ce genre de probl&#233;matique de compression :

- [Clean CSS](http://www.cleancss.com/)
- [CSS Compressor](http://www.codenothing.com/css-compressor/)

Ces deux outils permettent de faire des optimisations tr&#232;s avanc&#233;es, tout en &#233;tant capable de g&#233;rer des niveaux plus o&#249; moins &#233;lev&#233;s de compression.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Hauteur d'un bloc de texte]]></title>
        <description><![CDATA[La taille d'un bloc de texte est toujours plus grande que le texte lui même... Pourquoi ?]]></description>
        <link>https://brunosabot.dev/posts/2011/hauteur-d-un-block-de-texte/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/hauteur-d-un-block-de-texte/</guid>
        <pubDate>Tue, 10 May 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Piq&#251;re de rappel pour certains, d&#233;couverte pour d&#8217;autres, vous avez peut-&#234;tre d&#233;j&#224; remarqu&#233; que dans ce cas :


```css
.bloc {  font-size: 20px;}
```


La hauteur du block div n&#8217;est pas de 20 pixel, mais &#171; plus &#187;. Ceci est, dans des cas d&#8217;int&#233;gration assez courants (positionnements relatifs) la source de probl&#232;mes.


Il est n&#233;anmoins assez difficile de d&#233;finir ce &#171; plus &#187;, qui semble &#234;tre variable en fonction de la police utilis&#233;e et du navigateur.


Par exemple sous Chrome et Firefox avec Arial :


| font-size | 30 | 29 | 28 | 27 | 26 | 25 | 24 | 23 | 22 | 21 | 20 |
| --------- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- | -- |
| height    | 36 | 35 | 34 | 33 | 31 | 31 | 30 | 28 | 27 | 25 | 25 |
| font-size | 19 | 18 | 17 | 16 | 15 | 14 | 13 | 12 | 11 | 10 | 9  |
| height    | 23 | 22 | 20 | 20 | 18 | 16 | 16 | 15 | 14 | 12 | 11 |


Le ratio est donc un calcul approximatif, probablement avec des arrondis, pas &#233;vident de connaitre donc la bonne hauteur du bloc.


C&#8217;est l&#224; que l&#8217;on fait intervenir le line-height. Il suffit de le rajouter Pour r&#233;soudre le probl&#232;me :


```css
.bloc {  font-size: 20px;  line-height: 20px;}
```


A pr&#233;sent, le div a bien une hauteur de 20 pixels de haut !]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[ORDER BY avec une jointure]]></title>
        <description><![CDATA[Le saviez-vous ? Le choix de l'ordre du tri a un incidence sur les performance du tri]]></description>
        <link>https://brunosabot.dev/posts/2011/order-by-avec-une-jointure/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/order-by-avec-une-jointure/</guid>
        <pubDate>Fri, 29 Apr 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[# Le saviez-vous ?


Le choix des champs pour l&#8217;ordre de tri est important dans une requ&#234;te SQL.


Prennez par exemple la requ&#234;te suivante :


```sql
SELECT *FROM `ma_table_a` aINNER JOIN `ma_table_b` b ON (a.`aid` = b.`aid`)ORDER BY b.`aid` DESC
```


En pla&#231;ant un index sur le champ b.`aid`, celui-ci sera utilis&#233; dans la requ&#234;te, et celle-ci sera tr&#232;s rapide.


En r&#233;alit&#233;, ce n&#8217;est pas le cas. L&#8217;utilisation de cet index n&#8217;est absolument pas bon et vous allez probablement autant de temps &#224; executer la requ&#234;te que s&#8217;il n&#8217;y en avait pas.


# Pourquoi ?


Dans ce genre de requ&#234;te, vous avez le tri qui est execut&#233; &#224; la fin de la requ&#234;te, juste avant le renvoi des donn&#233;es.


Le tri est execut&#233; sur une table temporaire qui ne contient pas d&#8217;index, et donc pas celui de la table `ma_table_b`.


# Comment corriger le probl&#232;me ?


Si l&#8217;on choisit un index de la premi&#232;re table `ma_table_a`, celle de la close FROM, le tri sera effectu&#233; avant la jointure, et l&#8217;on peut ainsi b&#233;n&#233;ficier totalement de l&#8217;index de la table plac&#233; sur `aid` :


```sql
SELECT *FROM `ma_table_a` aINNER JOIN `ma_table_b` b ON (a.`aid` = b.`aid`)ORDER BY a.`aid` DESC
```


Sur ce probl&#232;me, en changeant la close ORDER BY par celle de la premi&#232;re table, je passe d&#8217;une requ&#234;te de plus de 5 secondes&#8230; &#224; 10ms.

- Moteur utilis&#233; : MyISAM
- Nombre d&#8217;enregistrements : 130 000 / table]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Optimisation de boucles JavaScript]]></title>
        <description><![CDATA[Parfois, le code le plus simple n'est pas le plus performant. C'est le cas des boucles JavaScript.]]></description>
        <link>https://brunosabot.dev/posts/2011/optimisation-de-boucles-javascript/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/optimisation-de-boucles-javascript/</guid>
        <pubDate>Fri, 22 Apr 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[N&#8217;&#233;tant pas rest&#233; au bout de la [soir&#233;e webperf](https://sites.google.com/a/survol.fr/webperf-user-group/actualites/soireedu21avril) du 21 avril, je ne me risquerais pas &#224; faire un compte rendu qui risquerait d&#8217;&#234;tre incomplet.


N&#233;anmoins, lors de cette soir&#233;e nous avons d&#233;couvert un morceau de code assez surprenant.


Pour se mettre dans le contexte, nous &#233;tions en train d&#8217;analyser le site de [20 minutes](http://www.20minutes.fr/), et Vincent Voyer &#224; d&#233;cid&#233; de lancer un Dynatrace.


En regardant les r&#233;sutlats, nous avons remarqu&#233; qu&#8217;un script avait un temps d&#8217;execution de&#8230; 1.1s, ce qui est bien entendu &#233;norme ! Ce script vennait du tag xiti. Celui-ci sera peut-&#234;tre corrig&#233;, mais il pourra servir de cas d&#8217;exemple sur une mauvaise pratique.


En fouillant dans le code on d&#233;couvre (en r&#233;indent&#233;) le code suivant :


```javascript
var elts = target.getElementsByTagName("*");for (var i = 0; i &#60; elts.length; i++) {  //...}
```


Pourquoi est-ce un probl&#232;me ? Avant de commencer &#224; expliquer le probl&#232;me, il est important de connaitre un point important. Pour celui-l&#224;, j&#8217;ai utilis&#233; [statsy](/posts/2010/statsy-v2-quelques-ajouts/).


```plain text
JS in HTML attributes: 1351 bytes
CSS in HTML attributes: 2790 bytes
JS in SCRIPT tag: 4111 bytes
CSS in STYLE tag: 69039 bytes
All innerHTML: 215207 bytes
# SCRIPT tag: 54
# STYLE tag: 1
# DOM elements: 2143
Cookie size: 340 bytes
```


Dans la liste des r&#233;sultats ci-dessus, on remarque que la page comporte 2143 &#233;lements. Le probl&#232;me est simple :


Le `getElementsByTagName` r&#233;cup&#232;re tout les &#233;l&#233;ments DOM. Ce qui demande beaucoup de traitement sur une page kilom&#233;trique comme celle d&#8217;un site de news.


Second probl&#232;me, la boucle effectu&#233;e sur ces &#233;l&#233;ments recalcule cette selection &#224; chaque it&#233;ration de la boucle. Les &#233;lements sont donc recalcul&#233;s 2143 fois. C&#8217;est simplement &#233;norme.


Comment corriger le probl&#232;me ? Pour le premier, il faut savoir si cette selection est vraiment importante. Qu&#8217;&#224;-t-on besoin de faire sur tout l&#8217;arbre DOM ?


Pour le second, il suffit de mettre en cache la taille du tableau, par exemple comme ceci :


```javascript
var elts = target.getElementsByTagName("*");for (var i = 0, len = elts.length; i &#60; len; i++) {  //...}
```


Et dans ces conditions, le recalcul ne sera pas refait pour chaque it&#233;ration.


Conclusion :


De plus en plus, les sites sont remplis de JavaScript. Ces am&#233;liorations peuvent sembler superflues, mais c&#8217;est ce qui peut faire la diff&#233;rence entre deux site.


Si vous n&#8217;en &#234;tes pas convaincus, pensez aux utilisateurs de netbook, de tablettes ou de mobile : Vous allez leur faire gagner un temps fou !]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Load average énorme !]]></title>
        <description><![CDATA[Quelles solutions pour détecter, analyser, reporter et corriger les problèmes de charge CPU ?]]></description>
        <link>https://brunosabot.dev/posts/2011/load-average-enorme/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/load-average-enorme/</guid>
        <pubDate>Sun, 20 Mar 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Ce matin, j&#8217;ai &#233;t&#233; confront&#233; &#224; des probl&#232;mes de lenteur serveur. Des temps de r&#233;ponses excessivement hauts, d&#8217;&#233;normes latences en SSH, un load average &#233;norme.


Quel a &#233;t&#233; la d&#233;marche pour r&#233;soudre ce probl&#232;me ? C&#8217;est l&#8217;objectif de ce billet de pr&#233;senter les probl&#232;mes rencontr&#233;s.


Tout d&#8217;abord, il faut savoir que j&#8217;ai mis en place un script de surveillance de la charge serveur. Celui-ci, qui tourne sur un cron, me pr&#233;vient lorsque le _load average_ d&#233;passe 1.


J&#8217;ai volontairement mis la valeur assez basse, pour anticiper au mieux tout probl&#232;me.


Si vous voulez en apprendre un peu plus sur ce qu&#8217;est le load average, je vous recommande [cet article](http://blog.scoutapp.com/articles/2009/07/31/understanding-load-averages).


Le script que j&#8217;utilise est le suivant :


```shell
max_loadavg=1date=`date '+%d-%m-%Y'`loadavg=$(cat /proc/loadavg | awk '{print $1}')if [ $(echo "$loadavg &#62;= $max_loadavg"|bc) -eq 1 ]thenSUBJECT="`hostname` ALERT LOAD AVERAGE $loadavg(&#62;$max_loadavg)"EMAIL="xxxxxxxxxxxx@xxxxxxx.xx"EMAILMESSAGE="Load Average : $loadavg on `date`"echo $EMAILMESSAGE | mail -s "$SUBJECT" "$EMAIL"fiexit
```


Ce matin donc, je re&#231;oit un mail pour me pr&#233;venir que la charge est trop importante. En effet, le message est le suivant:


```plain text
Load Average : 29.28 on dimanche 20 mars 2011, 07:08:01 (UTC+0100)
```


Je lance mon putty, et j&#8217;execute la commande suivante :


```plain text
root@r00000:~$ uptime
07:15:59 up 7:06, 2 users, load average: 26.09, 29.07, 31.01
```


Le probl&#232;me est bien toujours pr&#233;sent. Je commence ma recherche :


```shell
top -b -n 1 | awk '{if (NR &#60;=7) print; else if ($8 == "D") {print; count++} } END {print "Total status D: "count}'
```


Cette commande [trouv&#233;e sur le net](http://www.linuxquestions.org/questions/red-hat-31/high-load-average-low-cpu-usage-615506/) me permet d&#8217;obtenir une liste de processus source du probl&#232;me.


Et l&#224; bingo, j&#8217;ai une liste consid&#233;rable de processus _cron_ dans ma liste : le probl&#232;me vient de l&#224;, il ne me reste plus qu&#8217;&#224; v&#233;rifier et &#224; r&#233;soudre.


Je prend mon mal en patience, le temps d&#8217;ouvrir l&#8217;&#233;dition de _crontab_, je commente toutes les lignes, et je relance. Plus de probl&#232;me, la charge se remet &#224; baisser, le soucis provient bien de l&#224;. Il ne me reste plus qu&#8217;&#224; fouiner un peu les executions du cron pour trouver le probl&#232;me et le corriger.


_crontab_ se relance&#8230; et pour l&#8217;instant, je touche du bois !]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Mettre en place un placeholder et son failback]]></title>
        <description><![CDATA[Le HTML5 apporte un nouvel attribut aux champs input text et assimilés : placeholder. Mais que faire sur les vieux navigateurs ?]]></description>
        <link>https://brunosabot.dev/posts/2011/mettre-en-place-un-placeholder-et-son-failback/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/mettre-en-place-un-placeholder-et-son-failback/</guid>
        <pubDate>Thu, 17 Mar 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Le HTML5 apporte un nouvel attribut aux champs input text et assimil&#233;s : placeholder.


Le principe de cet attribut est simple : lorsqu&#8217;un champ est vide, on affiche un texte de remplacement pour indiquer ce que doit contenir le champ. Il est tr&#232;s utilis&#233; au niveau applicatif, comme dans l&#8217;omnibar de Firefox par exemple :


![placeholder1.jpeg](https://storage.googleapis.com/brunosabot.dev/img/placeholder1.jpeg)


Placeholder de l&#8217;omnibar de Firefox


Le HTML5 nous permet de l&#8217;utiliser nativement. Cependant, cet attribut n&#8217;est pas encore disponible sous tout les navigateurs. L&#8217;objectif fix&#233; par cet article est de mettre en place le syst&#232;me ainsi qu&#8217;un bon failback.


Pour effectuer tout ceci, j&#8217;utiliserais jQuery. Le script peut bien entendu &#234;tre &#233;tendu &#224; tout les framework, et je vous invite &#224; mettre en commentaire le syst&#232;me avec d&#8217;autres syst&#232;mes.


Si l&#8217;on veut faire simple et tout g&#233;rer en JavaScript, il suffit d&#8217;&#233;crire un code tel que le suivant :


```javascript
$('input[type="text"]')  .blur(function () {    if (this.value == "") {      this.value = this.defaultValue;    }  })  .focus(function () {    if (this.value == this.defaultValue) {      this.value = "";    }  });
```


Ce mod&#232;le pr&#233;sente une limitation, mais nous verons &#231;a plus tard.


Le premier point &#224; mettre en place est la reconnaissance de la pr&#233;sence du placeholder. On va ici utiliser la m&#234;me m&#233;thode que Modernizr :


```javascript
var input = document.createElement("input");if (!("placeholder" in input)) {  // Faire ici le traitement pour les navigateurs ne reconnaissant pas le placeholder  // Il suffit d'y placer le code pr&#233;c&#233;dent par exemple !} else {  // On supporte le placeholder : on vide la valeur par d&#233;faut  $('input[type="text"]').val("");}
```


Et voil&#224;, le syst&#232;me est en place et il suffit de mettre en application le placeholder sur le champ input :


```html
&#60;input type="text" placeholder="Exemple" /&#62;
```


Aller plus loin :


Les plus perspicaces vont tout de suite remarquer la limitation de cette m&#233;thode : Obligation de vider la valeur du champ de formulaire, dont pas de valeur par d&#233;faut possible, Impossible de rentrer le m&#234;me texte que le placeholder, &#8230;


On peut aussi parler du l&#8217;interdiction du JavaScript, mais je consid&#232;rerais cela comme un d&#233;tail sans importance : les plus grands sites (gmail, facebook, &#8230;) obligeant &#224; l&#8217;utiliser, j&#8217;ommetrais les visteurs r&#233;calcitrants.


Les probl&#232;mes expos&#233;s, attaquons la premi&#232;re am&#233;lioration :


Afin d&#8217;&#233;viter les probl&#232;mes de valeurs par d&#233;faut, je vais utiliser la trop peu connue fonction de jQuery data.


```javascript
$('input[type="text"]').each(function () {  var $this = $(this);  var $default = $this.data("placeholder");  if ($default === undefined) {    return;  }  $this.data("placeholderactive", true);  $this    .focus(function () {      if ($this.data("placeholderactive") === true &#38;&#38; $this.val() == $default) {        $this          .val("")          .removeClass("placeholderactive")          .addClass("placeholderinactive");        $this.data("placeholderactive", false);      }    })    .blur(function () {      if ($this.data("placeholderactive") === false &#38;&#38; $this.val() == "") {        $this          .val($default)          .removeClass("placeholderinactive")          .addClass("placeholderactive");        $this.data("placeholderactive", true);      }    })    .val($default)    .removeClass("placeholderinactive")    .addClass("placeholderactive");});
```


Et son nouveau champ input associ&#233; :


```html
&#60;input type="text" data-placeholder="Exemple" /&#62;
```


Pour mettre un style diff&#233;rent sur le champ placeholder, j&#8217;ai utilis&#233; deux classes : placeholderactive et placeholderinactive qui s&#8217;occupent respectivement d&#8217;avoir un style en mode &#171; placeholder &#187; et un style hors de ce mode.


A vous de jouer et d&#8217;impl&#233;menter ce nouvel attribut HTML5 sur vos site !


N&#8217;h&#233;sitez pas &#224; partager vos m&#233;thodes pour les autres framework JS ou les am&#233;liorations &#224; apporter &#224; mon script jQuery.]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Laissez votre iPhone choisir pour vous]]></title>
        <description><![CDATA[Lorsque vous ne savez pas quoi choisir... Il y a une app pour ça !]]></description>
        <link>https://brunosabot.dev/posts/2011/laissez-votre-iphone-choisir-pour-vous/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2011/laissez-votre-iphone-choisir-pour-vous/</guid>
        <pubDate>Sun, 13 Feb 2011 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Il m&#8217;a souvent &#233;t&#233; utile d&#8217;avoir un script pour tirer al&#233;atoirement une valeur parmi plusieurs. Le pile ou face &#233;tant limit&#233; &#224; 2 choix, et comme je n&#8217;ai pas toujours de d&#233;s sur moi, j&#8217;ai d&#233;cid&#233; de d&#233;velopper une application pour mon iPhone, qui me permettrais de faire les choix &#224; ma place.


Le fonctionnement est simple : Il suffit de rentrer les valeurs d&#233;sir&#233;es, puis de cliquer sur le header &#171; Random it! &#187; pour voir apparaitre le r&#233;sultat.


A quoi ressemble l&#8217;application :


![screen.jpeg](https://storage.googleapis.com/brunosabot.dev/img/screen.jpeg)


A quoi ressemble l&#8217;application


Pour se rendre sur l&#8217;application, il suffit de naviger &#224; l&#8217;adresse suivante depuis le smartphone : [http://tools.brunosabot.com/random](http://tools.brunosabot.com/random)


La th&#233;orie veut que le fonctionnement soit le m&#234;me sous un autre OS mobile comme Android, mais ce n&#8217;est pas test&#233;.


Edit : Le lien ne fonctionne plus, vous pouvez maintenant utiliser [Answwr](https://answwr.com/)]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Les « includes » et les « require » de PHP]]></title>
        <description><![CDATA[Vous êtes peut-être déjà tombés sur le problème suivant : lorsque l’on utilise un include, le caractère « null » coupe l’inclusion du fichier.]]></description>
        <link>https://brunosabot.dev/posts/2010/les-includes-et-les-require-de-php/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2010/les-includes-et-les-require-de-php/</guid>
        <pubDate>Wed, 29 Dec 2010 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Avez-vous d&#233;j&#224; utilis&#233; les includes et les require de PHP ?


Si oui, vous &#234;tes peut-&#234;tre d&#233;j&#224; tomb&#233;s sur le probl&#232;me suivant : lorsque l&#8217;on utilise un include, le caract&#232;re &#171; null &#187; coupe l&#8217;inclusion du fichier.


Le probl&#232;me est principalement pos&#233; lors de l&#8217;inclusion dynamique d&#8217;un fichier. Imaginez un simple sur le site example.com :


```php
include $_GET['file'];
```


Il suffit d&#8217;utiliser l&#8217;URL http://example.com/?file=.htaccess%00 pour acc&#233;der au contenu du fichier .htaccess, et &#233;ventuellement bien pire !


Pour r&#233;soudre le probl&#232;me ? V&#233;rifier que le fichier inclus est bien un fichier que l&#8217;on autorise &#224; l&#8217;inclusion, avec &#233;ventuellement un tableau des fichiers autoris&#233;s, tester que le fichier est bien un fichier PHP (et donc que le contenu sera interpr&#233;t&#233;), et toute autre v&#233;rification, qui bien entendu ne sera pas superflue.


Mais &#233;videment, vous faites d&#233;j&#224; les v&#233;rifications n&#233;cessaires 😉]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Statsy v2 – Quelques ajouts]]></title>
        <description><![CDATA[De nouvelles fonctionnalités pour améliorer le rapport Statsy]]></description>
        <link>https://brunosabot.dev/posts/2010/statsy-v2-quelques-ajouts/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2010/statsy-v2-quelques-ajouts/</guid>
        <pubDate>Wed, 27 Oct 2010 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[[Stoyan Stefanov](http://www.phpied.com/) &#224; pr&#233;sent&#233; [Stasty](http://www.phpied.com/statsy-more-data-points-for-markup-quality/), un bookmarklet qui permet d&#8217;obtenir tr&#232;s rapidement des informations notamment sur les style et script pr&#233;sents en inline sur une page.


Cependant, il manque sur ce script quelques informations importantes, facilement r&#233;cup&#233;rables.


J&#8217;ai donc cr&#233;&#233; un nouveau bookmarklet avec des infomations un peu plus compl&#232;tes :


```javascript
(function() {  var jsattribs = [    'onbeforeunload',    'onclick',    'ondblclick',    'onerror'    'onload',    'onmousedown',    'onmousemove',    'onmouseout',    'onmouseover',    'onmouseup',    'onunload',  ],  cssattribs = [    'style'  ];  var all_elems = document.getElementsByTagName('*');  function getAttribsSize(attribs) {    var value = '',      attr = '',      cnt = 0;    for (var i = 0; i &#60; all_elems.length; i++) {      for (var j = 0; j &#60; attribs.length; j++) {        attr = attribs[j];        value = all_elems[i].getAttribute(attr);        if (value &#38;&#38; typeof value === 'string') {          cnt += attr.length;          cnt += 3; // ="..."          cnt += value.length;        }      }    }    return cnt;  }  function getInlineSize(tag) {    var s = 0,      all = document.getElementsByTagName(tag);    for (var i = 0; i &#60; all.length; i++) {      s += all[i].innerHTML.length;    }    return s;  }  var jsatt = getAttribsSize(jsattribs);  var cssatt = getAttribsSize(cssattribs);  var msg = [];  msg.push('JS in HTML attributes: '+jsatt+' bytes');  msg.push('CSS in HTML attributes: '+cssatt+' bytes');  msg.push('JS in SCRIPT tag: '+getInlineSize('script')+' bytes');  msg.push('CSS in STYLE tag: '+getInlineSize('style')+' bytes');  msg.push('All innerHTML: '+document.documentElement.innerHTML.length+' bytes');  msg.push('# SCRIPT tag: '+document.getElementsByTagName('script').length);  msg.push('# STYLE tag: '+document.getElementsByTagName('style').length);  msg.push('# DOM elements: '+all_elems.length);  msg.push('Cookie size: '+document.cookie.length+' bytes');  alert(msg.join("n"));})();
```


N&#8217;h&#233;sitez pas &#224; faire vos suggestions !

- * _UPDATE 30/11/2010_ **

Suite &#224; la demande de jpvincent, petit explicatif des diff&#233;rentes informations remont&#233;es :


`JS in HTML attributes` : Cette information remonte tout le code JavaScript pr&#233;sent directement dans les balises HTML en tant qu&#8217;attribut. Par exemple, une page qui contiendrait `&#60;h1 onmouseover="alert('H1');"&#62;&#60;/h1&#62;` ajouterait 26 bits &#224; JS in HTML attributes


`CSS in HTML attributes` : M&#234;me proc&#233;d&#233; que pour les JavaScript d&#233;taill&#233;s un peu plus haut, en utilisant cette fois le contenu des attributs style (ex. `style="color:#f00;"`)


`JS in SCRIPT tag` : Cette information remonte le nombre de bits du code JavaScript pr&#233;sent au sein de balises


`JCSS in STYLE tag` : M&#234;me principe que la propri&#233;t&#233; pr&#233;c&#233;dente, cette information remonte le nombre de bits du code CSS pr&#233;sent au sein de balises `&#60;style&#62;&#60;/style&#62;`


`# SCRIPT tag` : Compte le nombre d&#8217;occurrence d&#8217;utilisation des balises `&#60;script&#62;` au sein de la page


`# STYLE tag` : Compte le nombre d&#8217;occurrence d&#8217;utilisation des balises `&#60;style&#62;` au sein de la page


`# DOM elements` : Compte le nombre d&#8217;&#233;l&#233;ments DOM au sein de la page


`Cookie size` : Retourne la taille en bit des cookies pr&#233;sent sur le fichier HTML, et donc probablement sur tout les fichiers statiques s&#8217;ils ne sont pas sur un domaine externe


De nouvelles fonctionnalit&#233;s pour am&#233;liorer le rapport Statsy]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Décompression de fichiers sous Linux]]></title>
        <description><![CDATA[Les formats de décompressions sous Linux sont assez compliqués à retenir, surtout étant donné le nombre de formats de compression existants.]]></description>
        <link>https://brunosabot.dev/posts/2010/decompression-de-fichiers-sous-linux/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2010/decompression-de-fichiers-sous-linux/</guid>
        <pubDate>Sun, 18 Jul 2010 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Les formats de d&#233;compressions sous Linux sont assez compliqu&#233;s &#224; retenir, surtout &#233;tant donn&#233; le nombre de formats de compression existants.


Dans un de ses tweets, [@zeroload](http://twitter.com/zeroload/status/17777627644) pose ce probl&#232;me.


J&#8217;utilise personnellement un script pour effectuer le travail. Celui-ci, r&#233;cup&#233;r&#233; il y a longtemps sur Internet et ajust&#233; &#224; mes besoins, il choisi son mode de d&#233;compression en fonction des formats du fichier, et ex&#233;cute la commande associ&#233;e.


Pour l&#8217;utiliser, il suffit de placer les lignes suivantes dans le fichier .bashrc :


```shell
extract () {    if [ -f $1 ] ; then        case $1 in            *.7z) 7z x $1 ;;            *.bz2) bunzip2 $1 ;;            *.gz) gunzip $1 ;;            *.rar) rar x $1 ;;            *.tar) tar xvf $1 ;;            *.tar.bz2) tar xvjf $1 ;;            *.tar.gz) tar xvzf $1 ;;            *.tbz2) tar xvjf $1 ;;            *.tgz) tar xvzf $1 ;;            *.Z) uncompress $1 ;;            *.zip) unzip $1 ;;            *) echo "Le format de compression de '$1' n'est pas support&#233;..." ;;        esac    else        echo "'$1' n'est pas un fichier !"    fi}
```


On peut bien entendu ajouter tr&#232;s facilement un nouveau format g&#233;r&#233; par le script, et pourquoi pas cr&#233;er une autre fonction pour g&#233;rer la compression suivant plusieurs formats !


NB: Pour recharger le fichier .bashrc, il suffit de se reconnecter au compte, ou de faire :


```shell
cd. .bashrc
```]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[La vitesse facteur du classement Google]]></title>
        <description><![CDATA[Les utilisateurs du web veulent de la rapidité. Google a décidé de prendre en compte la vitesse comme facteur de référencement.]]></description>
        <link>https://brunosabot.dev/posts/2009/la-vitesse-facteur-du-classement-google/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2009/la-vitesse-facteur-du-classement-google/</guid>
        <pubDate>Tue, 01 Dec 2009 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Les utilisateurs du web veulent de la rapidit&#233;, et nombreux sont ceux qui quittent une page trop longue &#224; charger. Pour proposer de meilleurs r&#233;sultats &#224; ses visiteurs, Google a d&#233;cid&#233; de prendre en compte la vitesse comme facteur de r&#233;f&#233;rencement.


Les innovations pour le web sont nombreuses ces derniers temps &#224; Mountain View, et une grande partie d&#8217;entre elles concernent la vitesse.


Majoritairement, les visiteurs d&#8217;un site prennent comme premier facteur de qualit&#233; pour la page la rapidit&#233; du site, avant de pr&#233;f&#233;rer le design puis le contenu. Un sondage men&#233; par webcopyplus[^1] nous informe que 50% des visiteurs privil&#233;gient la c&#233;l&#233;rit&#233;, contre 25% pour le design et le contenu.


Google le sait, et travaille dans le domaine de la vitesse.


En septembre 2008, Google sort son navigateur web, Chrome, dont les caract&#233;ristiques mises en avant par la soci&#233;t&#233; sont la rapidit&#233; du lancement de l&#8217;application, de t&#233;l&#233;chargement des donn&#233;es, de la recherche[^2]. D&#8217;apr&#232;s des tests men&#233;s et expos&#233;s sur lifehacker[^3], ces revendications sont en effet v&#233;ridiques. Un premier point pour Google qui estime donc que la vitesse est suffisamment importante pour s&#8217;ins&#233;rer sur le march&#233; surcharg&#233; des navigateurs.


Google ne s&#8217;arr&#234;te pas l&#224;, avec la mise en place d&#8217;un page d&#233;di&#233;e[^4] &#224; la vitesse des sites en Juin 2009, proposant outils et conseils pour optimiser son site, &#224; la mani&#232;re de Yahoo![^5] chez qui qui la soci&#233;t&#233; de Moutain View a d&#8217;ailleurs recrut&#233; l&#8217;un des membres les plus importants : Steve Souders. Celui-ci s&#8217;est attel&#233; &#224; la construction d&#8217;un outil similaire &#224; YSlow qu&#8217;il avait cr&#233;&#233; chez Yahoo !. Google Page Speed[^6] est une extension du plugin Firefox Firebug[^7] d&#8217;aide &#224; l&#8217;optimisation des sites web, dans le but de les rendre plus rapides. Cet outil reprend en grande partie les recommandations (Best Practices) de Yahoo pour un site plus rapide.


L&#8217;une des r&#232;gles les plus importantes pour l&#8217;optimisation des sites est la minification des fichiers CSS et JavaScript. Le 5 Novembre 2009[^8], Google &#224; lanc&#233; un outil de minification, Closure Compiler et une librairie JavaScript Closure Library[^9] pour r&#233;pondre &#224; cette r&#232;gle.


La derni&#232;re implication de Google dans la vitesse du web concerne les protocoles d&#8217;&#233;change de donn&#233;es, l&#8217;id&#233;e du g&#233;ant de Mountain View est de remplacer l&#8217;actuel protocole HTTP par un nouveau, SPeeDY[^10], qui permettrais de gagner 50% sur le temps de chargement des pages web. La sortie officielle des outils n&#8217;a pas &#233;t&#233; encore r&#233;v&#233;l&#233;e, mais elle ne saurait tarder.


Tout ces points permettent d&#8217;imaginer que Google pr&#233;f&#232;re la rapidit&#233;, et donc l&#8217;inclurait dans son algorithme de recherche.


Les suppositions se sont r&#233;v&#233;l&#233;es justes, apr&#232;s l&#8217;annonce de Matt Cutts[^11], qui r&#233;v&#232;le que le nouveau moteur de recherche de Google (cafeine) prend &#224; pr&#233;sent en compte la vitesse parmi les param&#232;tres d&#8217;indexation.


Pour un site web mieux index&#233;, d&#233;veloppez un site web rapide !


**Mise &#224; jour, Jeudi 3 D&#233;cembre 2009**


Google vient de lancer[^12] un nouvel outil pour aider les webmasters &#224; am&#233;liorer la rapidit&#233; de leur site.


Cet outil tire ses informations de Google Page Speed que nous avions pr&#233;c&#233;demment abord&#233;, et est inclus dans l&#8217;outil Webmaster Tools[^13].


L&#8217;outil est encore en phase de test, mais il donne de nombreuses informations sur les lacunes d&#8217;optimisations d&#8217;un site, page par page, ainsi que des caract&#233;ristiques plus g&#233;n&#233;ralistes sur la vitesse de chargement moyenne des pages d&#8217;un site et une comparaison avec les autres pages index&#233;es dans le moteur de recherche.


**Mise &#224; jour, Vendredi 4 D&#233;cembre 2009**


De nouveau, Google propose un service pour optimiser la vitesse de chargement des sites.
Tr&#232;s controvers&#233;, il s&#8217;agit du Google Public DNS[^14], un service qui en cens&#233; &#233;conomiser de pr&#233;cieuses millisecondes dans la r&#233;solution DNS des sites Internet.


Le service serait plus rapide que OpenDNS par exemple, mais les chiffres sont assez diversifi&#233;s selon les premi&#232;res &#233;tudes. 


Mais qu&#8217;importe la solution choisie, Google cherche une fois de plus la rapidit&#233; !


(^1): http://www.webcopyplus.com/content/view/245/9/


(^2): http://www.google.com/chrome


(^3): http://lifehacker.com/5395555/browser-speed-tests-the-windows-7-results


(^4): http://code.google.com/speed


(^5): http://developer.yahoo.com/performance/


(^6): http://code.google.com/speed/page-speed/


(^7): http://getfirebug.com/


(^8): http://googlecode.blogspot.com/2009/11/introducing-closure-tools.html


(^9): http://code.google.com/closure/


(^10): http://sites.google.com/a/chromium.org/dev/spdy/


(^11): http://www.webpronews.com/topnews/2009/11/13/google-page-speed-may-be-a-ranking-factor-in-2010


(^12): http://googlewebmastercentral.blogspot.com/2009/12/how-fast-is-your-site.html


(^13): https://www.google.com/webmasters/tools/


(^14): http://googlecode.blogspot.com/2009/12/introducing-google-public-dns-new-dns.html]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Rendre l’URL de recherche de Google plus lisible]]></title>
        <description><![CDATA[Matt Cutts, responsable de la webspam team de google à posté sur son twitter Une méthode pour rendre les URL de recherche de Google plus jolies.]]></description>
        <link>https://brunosabot.dev/posts/2009/rendre-l-url-de-recherche-de-google-plus-lisible/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2009/rendre-l-url-de-recherche-de-google-plus-lisible/</guid>
        <pubDate>Mon, 30 Nov 2009 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Matt Cutts, responsable de la webspam team de google &#224; post&#233; sur son twitter [Une m&#233;thode pour rendre les URL de recherche de Google plus jolies](http://twitter.com/mattcutts/status/6188628631).


Lorsque vous faites une recherche, vous tombez facilement sur une URL du type http://www.google.com/search?source=ig&#38;hl=en&#38;rlz=&#38;q=google&#38;aq=f&#38;oq=&#38;aqi=g-p3g7 qui n&#8217;est au final pas tr&#232;s lisible. Cette m&#233;thode permet de transformer cette URL en une plus compr&#233;hensible pour l&#8217;humain et aillant les m&#234;mes r&#233;sultats : http://www.google.com/search?q=google.


Malheureusement, dans l&#8217;exemple que je donne, cette m&#233;thode est bugg&#233;e et ne retourne pas un bon r&#233;sultat. J&#8217;ai donc corrig&#233; ce bookmarklet afin d&#8217;avoir un r&#233;sultat correct en toute situation.


Pour les personnes qui voudraient &#233;ventuellement analyser le code, le voici en version non minimifi&#233;e :


```javascript
javascript: (function () {  var windowLocationHref = window.location.href;  var domainName = window.location.hostname;  var myPathname = window.location.pathname;  var searchArray = windowLocationHref.split("&#38;");  var mySearch = "";  for (myElement in searchArray) {    if (searchArray[myElement].match(/^q=/)) {      mySearch = searchArray[myElement];    }  }  var searchOnly = domainName + myPathname + "?" + mySearch;  self.location.href = "http://" + searchOnly;})();
```]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[PHP : Warning: include_once(1)]]></title>
        <description><![CDATA[Lors de la créations de vos sites Internet, vous avez peut-être un jour obtenu l’erreur suivante : Warning: include_once(1)]]></description>
        <link>https://brunosabot.dev/posts/2008/php-warning-include_once-1/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2008/php-warning-include_once-1/</guid>
        <pubDate>Mon, 11 Aug 2008 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Lors de la cr&#233;ations de vos sites Internet, vous avez peut-&#234;tre un jour obtenu l&#8217;erreur suivante :


```plain text
Warning: include_once(1) [function.include-once]: failed to open stream: No such file or directory in Votre chemin
```


Cette erreur est due &#224; l&#8217;utilisation des fonction `die` ou `exit` combin&#233;e &#224; `include`, `include_once`, `require` ou `require_once` dans votre code. Un exemple vaut mieux que de longues explications :


```php
include_once('fichier.php') or die('Impossible d\'ouvrir le fichier');
```


L&#8217;exemple ci-dessus est incorrect, pr&#233;f&#233;rez-lui donc le code suivant :


```php
if (!include_once('fichier.php')) {  echo 'Impossible d'ouvrir le fichier';}
```


La seule explication que j&#8217;ai pu trouver de cette erreur est que la fonction include peut s&#8217;&#233;crire


```php
include 'fichier.php';
```


ce qui ne correspond pas &#224; l&#8217;appel d&#8217;une fonction. Ainsi, PHP interpr&#232;terais d&#8217;abord `'fichier.php' or die('Erreur');`, puis ferrait une inclusion de ce r&#233;sultat (qui vaut toujours le bool&#233;en VRAI).]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[VoilaBot attaque les sites !]]></title>
        <description><![CDATA[Depuis quelques temps, le robot de Voila, VoilaBot Beta 1.2, se met à crawler les sites Internet... Mais un peu trop.]]></description>
        <link>https://brunosabot.dev/posts/2008/voilabot-attaque-les-sites/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2008/voilabot-attaque-les-sites/</guid>
        <pubDate>Thu, 07 Aug 2008 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Depuis quelques temps, le robot de Voila, VoilaBot Beta 1.2, se met &#224; crawler les sites Internet.


Normal me direz-vous&#8230; Cependant, la fr&#233;quence de crawl est bien trop &#233;lev&#233;e, on a ainsi une nouvelle connexion au serveur toutes les secondes. Le nombre de connexions devient vite excessif, et le serveur surcharg&#233; prend trop de temps &#224; r&#233;pondre, voire m&#234;me ne r&#233;pond plus.


Les IP concern&#233;es par le VoilaBot sont les suivantes :


```plain text
193.252.149.15
193.252.149.16
81.52.143.15
81.52.143.16
```


Ces IP sont associ&#233;es respectivement au noms de domaine suivants : natcrawlbloc03.net.s1.fti.net, natcrawlbloc01.net.s1.fti.net, natcrawlbloc04.net.s1.fti.net, natcrawlbloc02.net.s1.fti.net


Les autres adresses de VoilaBot semblent &#234;tre inactives depuis un moment.


La premi&#232;re solution cens&#233;e bloquer les bot des moteurs de recherche est de cr&#233;er un fichier robots.txt, et d&#8217;interdire l&#8217;acc&#232;s au site pour certains moteur. Il faut donc uploader un fichier nomm&#233; robots.txt &#224; la racine du site qui contiendrait le code suivant :


```plain text
User-agent: VoilaBot
Disallow: /
```


Malheureusement, cette solution semble inutile puisque VoilaBot semble passer &#224; travers et ignore donc les normes d&#233;finies par l&#8217;ensemble des moteurs de recherche.


La solution suivante serait de bloquer l&#8217;acc&#232;s au niveau du fichier .htaccess, de la mani&#232;re suivante :


```plain text
deny from 193.252.149.15
deny from 193.252.149.16
deny from 81.52.143.15
deny from 81.52.143.16
```


Cette solution est maintenant efficace, mais le probl&#232;me est que le serveur va tout de m&#234;me recevoir la connexion des IP d&#233;sign&#233;es et donc, il y aura quand m&#234;me de la surcharge au niveau du serveur.


La derni&#232;re solution et la plus efficace est d&#8217;utiliser le firewall IPTABLES. Il faut pour cela &#234;tre sur serveur d&#233;di&#233; Linux et avoir les acc&#232;s root puisque nous allons configurer le firewall.


La documentation pour le t&#233;l&#233;charger et l&#8217;installer est disponible sur netfilter.org


Une fois install&#233;, on rejette les adresses de VoilaBot en entrant les commandes shell suivantes:


```plain text
iptables -I INPUT -s 193.252.149.15 -j DROP
iptables -I INPUT -s 193.252.149.16 -j DROP
iptables -I INPUT -s 81.52.143.15 -j DROP
iptables -I INPUT -s 81.52.143.16 -j DROP
```


Cette fois, VoilaBot ne peut plus acc&#232;der au serveur, le probl&#232;me est r&#233;gl&#233;, et tout &#224; coup, les pages se chargent enfin &#224; une vitesse normale.


On peut aussi cr&#233;er un fichier de configuration pour ex&#233;cuter automatiquement ces requ&#234;tes au d&#233;marrage du serveur qui, dans l&#8217;hypoth&#232;se de cr&#233;ation d&#8217;un fichier de configuration /etc/init.d/firewall, peut ressembler &#224; ceci :


```shell
#!/bin/sh# chkconfig: 3 21 91# description: FirewallIPT=/sbin/iptablescase "$1" instart)$IPT -I INPUT -s 193.252.149.15 -j DROP$IPT -I INPUT -s 193.252.149.16 -j DROP$IPT -I INPUT -s 81.52.143.15 -j DROP$IPT -I INPUT -s 81.52.143.16 -j DROPecho "Lancement du firewall ... OK"exit 0;;stop)$IPT -F INPUTecho "Arr&#234;t du firewall ....... OK"exit 0;;restart)/etc/init.d/firewall stop/etc/init.d/firewall startexit 0;;*)echo "Usage: /etc/init.d/firewall {start|stop|restart}"exit 1;;esac
```


Il suffit donc d&#8217;ex&#233;cuter les commandes :

- Pour d&#233;marrer le firewall : /etc/init.d/firewall start
- Pour arr&#234;ter le firewall : /etc/init.d/firewall stop
- Pour red&#233;marrer le firewall : /etc/init.d/firewall restart

Pour conclure, on peut dire qu&#8217;il est dommage qu&#8217;un moteur de recherche fran&#231;ais nuise ainsi aux sites Internet&#8230; Pour rattraper son retard ? Parce qu&#8217;il est mal configur&#233; ? Des questions &#224; poser aux d&#233;veloppeurs du VoilaBot&#8230;]]></content:encoded>
      </item>
      <item>
        <title><![CDATA[Internet Explorer : Opération abandonnée]]></title>
        <description><![CDATA[Le message « Internet Explorer ne peut pas ouvrir le site » « Opération abandonnée ! »]]></description>
        <link>https://brunosabot.dev/posts/2008/internet-explorer-operation-abandonnee/</link>
        <guid isPermaLink="false">https://brunosabot.dev/posts/2008/internet-explorer-operation-abandonnee/</guid>
        <pubDate>Thu, 07 Aug 2008 00:00:00 GMT</pubDate>
        <content:encoded><![CDATA[Si l&#8217;on prend des navigateurs diff&#233;rents, on n&#8217;obtient pas forcement la m&#234;me chose avec le m&#234;me code. C&#8217;est r&#233;guli&#232;rement le cas avec Internet Explorer et Mozilla Firefox.


J&#8217;ai r&#233;cemment eu des complications avec un code JavaScript. Le message indiquait &#171; Internet Explorer ne peut pas ouvrir le site &#187; &#171; Op&#233;ration abandonn&#233;e ! &#187;


La raison de se probl&#232;me r&#233;side dans le fait que du code JavaScript est ex&#233;cut&#233; avant le chargement complet de la page. Internet Explorer ne le supporte pas et bloque son chargement. Pour parer ce probl&#232;me, il existe plusieurs m&#233;thodes, suivant s&#8217;il l&#8217;on utilise un Framework tel que prototype.js ou si l&#8217;on n&#8217;ins&#232;re que quelques bout ind&#233;pendant de script dans ses pages.


Pour la premi&#232;re solution, je pars du principe que l&#8217;on utilise propotype.js comme framework.


Il suffit de remplacer le code actuel par :


```javascript
event.observe(window, "load", function () {  // Ins&#233;rer le script ici});
```


Ainsi, la fonction faisant planter Internet Explorer ne s&#8217;ex&#233;cute qu&#8217;apr&#232;s le chargement de la page et l&#8217;on peut souffler.


La deuxi&#232;me mani&#232;re consiste &#224; modifier soi-m&#234;me le load de la fonction.


```javascript
window.onload = function () {  // Ins&#233;rer le script ici};
```


Afin de coder proprement et permettre des ajouts futurs simple, nous allons ajouter la fonction au chargement de la page et non la remplacer comme il est habituellement fait et comme je l&#8217;ai montr&#233; ci-dessus. A la premi&#232;re syntaxe, pr&#233;f&#233;rez :


```javascript
window.onload += function () {  // Ins&#233;rer le script ici};
```


Nous faisons ici une concat&#233;nation et la fonction est ajout&#233;e aux autres ex&#233;cut&#233;s en cas de chargement de la page.


Et voil&#224;, un probl&#232;me de compatibilit&#233; r&#233;gl&#233; !]]></content:encoded>
      </item>
  </channel>
  </rss>