Skip to content

How Pinia is Created ​

Pinia Package Directory Structure ​

Understanding the structure of the Pinia source code can provide valuable insight into its design. The core Pinia package directory (available at https://github.com/vuejs/pinia/tree/v3/packages/pinia/src) is laid out as follows:

bash
createPinia.ts
env.ts
global.d.ts
globalExtensions.ts
hmr.ts
index.ts
mapHelpers.ts
rootStore.ts
store.ts
storeToRefs.ts
subscriptions.ts
types.ts

As you can see, the main logic is primarily found in createPinia.ts and store.ts. Compared to the directory structures of larger frameworks, the Pinia source directory is relatively straightforward, making it quite accessible for developers looking to understand its core implementation files.

Understanding Pinia's Core Mechanism ​

The Challenge of Global Reactive State ​

When implementing global state in a Vue application, an initial and seemingly easy approach might involve using simple, globally exported reactive variables directly within a separate file.

For example, you might define your state and basic mutations like this:

typescript
// store.ts
import { reactive } from 'vue';

export const counterState = reactive({
  count: 0
});

export function increment() {
  counterState.count++;
}

export function decrement() {
  counterState.count--;
}

Then, you would import this state and its functions into your components:

typescript
// Component.vue
import { counterState, increment, decrement } from './store';
import { defineComponent } from 'vue';

export default defineComponent({
  setup() {
    return {
      counterState,
      increment,
      decrement
    };
  }
});

This method achieve global state sharing, as all components importing counterState will reference the same underlying reactive object. However, this simple global export pattern has a critical flaw, particularly in Server-Side Rendering (SSR) environments: Cross-Request State Pollution.

Cross-Request State Pollution is a significant security vulnerability specific to SSR applications using shared singleton objects. It occurs when the same instance of a shared state object is inadvertently reused across multiple independent server requests from different users. This can lead to sensitive data from one user's request being exposed or leaked to another user, as they might end up reading or modifying the same state instance on the server.

The Role of Provide/Inject ​

To address the Cross-Request State Pollution issue in SSR and safely expand Vue's reactivity system in a way that is isolated per user request on the server, Pinia utilizes Vue's built-in Provide/Inject mechanism as a fundamental core pattern.

Vue's Provide/Inject allows data to be provided by an ancestor component (typically the root application instance in Pinia's case) and injected by any descendant component within that component tree. The provided example is a basic demonstration of Vue's Provide/Inject like this:

typescript
// parent
<script setup>
import { provide, ref } from 'vue'
const message = ref('Hello from parent!')
provide('messageKey', message)
</script>

// child.vue
<script setup>
import { inject } from 'vue'
const injectedMessage = inject('messageKey')
</script>

<template>
  <p>Injected message: {{ injectedMessage }}</p>
</template>

The createPinia function in Pinia is responsible for creating the core Pinia instance. This instance is designed to be unique for each application instance, which is crucial for SSR. By installing this Pinia instance on the root of your Vue application using app.use(pinia), this unique instance is then provided throughout the application tree via Vue's Provide/Inject mechanism. All components can then inject and access this specific Pinia instance.

When you call a store function defined by defineStore (like useCounterStore()) within a component's setup function, Pinia internally uses the injected Pinia instance to find or create the actual store instance for that specific application tree. This pattern effectively isolates the store instance to the component tree associated with a single user request in SSR. This usage pattern feels very similar to Vue 3's Composables, and you can conceptually think of Pinia providing store instances by making them accessible through a composable-like function (use...Store) via the injected Pinia instance.

Here's a simplified representation of this flow:

Limitations of Provide/Inject Alone (Discussed Later) ​

While Provide/Inject is the core mechanism of Pinia, maintaining the correct context in all cases can be challenging with Provide/Inject alone, especially in SSR scenarios involving Vue Router middleware. in detail in a later section.

Released under the MIT License.