"scripts": {
"build": "rollup -c rollup.config.js",
"build:dts": "api-extractor run --local --verbose",
+ "size": "rollup -c size-checks/rollup.config.js && node scripts/check-size.js",
"release": "bash scripts/release.sh",
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s -r 1",
"lint": "prettier -c --parser typescript \"{src,__tests__,e2e}/**/*.[jt]s?(x)\"",
"@rollup/plugin-replace": "^2.3.3",
"@types/jest": "^26.0.14",
"@types/node": "^14.11.2",
+ "@vue/devtools-api": "^6.0.0-beta.2",
+ "brotli": "^1.3.2",
"codecov": "^3.6.1",
"conventional-changelog-cli": "^2.1.0",
"jest": "^26.4.2",
--- /dev/null
+const fs = require('fs')
+const path = require('path')
+const chalk = require('chalk')
+const { gzipSync } = require('zlib')
+const { compress } = require('brotli')
+
+function checkFileSize(filePath) {
+ if (!fs.existsSync(filePath)) {
+ return
+ }
+ const file = fs.readFileSync(filePath)
+ const minSize = (file.length / 1024).toFixed(2) + 'kb'
+ const gzipped = gzipSync(file)
+ const gzippedSize = (gzipped.length / 1024).toFixed(2) + 'kb'
+ const compressed = compress(file)
+ const compressedSize = (compressed.length / 1024).toFixed(2) + 'kb'
+ console.log(
+ `${chalk.gray(
+ chalk.bold(path.basename(filePath))
+ )} min:${minSize} / gzip:${gzippedSize} / brotli:${compressedSize}`
+ )
+}
+
+checkFileSize(path.resolve(__dirname, '../size-checks/dist/small.js'))
--- /dev/null
+import path from 'path'
+import ts from 'rollup-plugin-typescript2'
+import replace from '@rollup/plugin-replace'
+import resolve from '@rollup/plugin-node-resolve'
+import commonjs from '@rollup/plugin-commonjs'
+import { terser } from 'rollup-plugin-terser'
+
+/** @type {import('rollup').RollupOptions} */
+const config = {
+ external: ['vue'],
+ output: {
+ file: path.resolve(__dirname, './dist/small.js'),
+ format: 'es',
+ },
+ input: path.resolve(__dirname, './small.js'),
+ plugins: [
+ replace({
+ __DEV__: false,
+ // this is only used during tests
+ __TEST__: false,
+ // If the build is expected to run directly in the browser (global / esm builds)
+ __BROWSER__: true,
+ // is targeting bundlers?
+ __BUNDLER__: false,
+ __GLOBAL__: false,
+ // is targeting Node (SSR)?
+ __NODE_JS__: false,
+ __VUE_PROD_DEVTOOLS__: false,
+ }),
+ ts({
+ check: false,
+ tsconfig: path.resolve(__dirname, '../tsconfig.json'),
+ cacheRoot: path.resolve(__dirname, '../node_modules/.rts2_cache'),
+ tsconfigOverride: {
+ compilerOptions: {
+ sourceMap: false,
+ declaration: false,
+ declarationMap: false,
+ },
+ exclude: ['__tests__', 'test-dts'],
+ },
+ }),
+ resolve(),
+ commonjs(),
+ terser({
+ format: {
+ comments: false,
+ },
+ module: true,
+ compress: {
+ ecma: 2015,
+ pure_getters: true,
+ },
+ }),
+ ],
+}
+
+export default config
--- /dev/null
+import { createPinia, defineStore } from '../dist/pinia.esm-bundler'
+
+createPinia()
+// @ts-ignore
+export default defineStore()
--- /dev/null
+import {
+ CustomInspectorNode,
+ CustomInspectorState,
+ setupDevtoolsPlugin,
+} from '@vue/devtools-api'
+import { App } from 'vue'
+import { getRegisteredStores, registerStore } from './rootStore'
+import { GenericStore, NonNullObject } from './types'
+
+function formatDisplay(display: string) {
+ return {
+ _custom: {
+ display,
+ },
+ }
+}
+
+let isAlreadyInstalled: boolean | undefined
+
+export function addDevtools(app: App, store: GenericStore, req: NonNullObject) {
+ registerStore(store)
+ setupDevtoolsPlugin(
+ {
+ id: 'pinia',
+ label: 'Pinia 🍍',
+ app,
+ },
+ (api) => {
+ api.on.inspectComponent((payload, ctx) => {
+ if (payload.instanceData) {
+ payload.instanceData.state.push({
+ type: '🍍 ' + store.id,
+ key: 'state',
+ editable: false,
+ value: store.state,
+ })
+ }
+ })
+
+ // watch(router.currentRoute, () => {
+ // // @ts-ignore
+ // api.notifyComponentUpdate()
+ // })
+
+ const mutationsLayerId = 'pinia:mutations'
+ const piniaInspectorId = 'pinia'
+
+ if (!isAlreadyInstalled) {
+ api.addTimelineLayer({
+ id: mutationsLayerId,
+ label: `Pinia 🍍`,
+ color: 0xe5df88,
+ })
+
+ api.addInspector({
+ id: piniaInspectorId,
+ label: 'Pinia 🍍',
+ icon: 'storage',
+ treeFilterPlaceholder: 'Search stores',
+ })
+
+ isAlreadyInstalled = true
+ } else {
+ // @ts-ignore
+ api.notifyComponentUpdate()
+ api.sendInspectorTree(piniaInspectorId)
+ api.sendInspectorState(piniaInspectorId)
+ }
+
+ store.subscribe((mutation, state) => {
+ // rootStore.state[store.id] = state
+ const data: Record<string, any> = {
+ store: formatDisplay(mutation.storeName),
+ type: formatDisplay(mutation.type),
+ }
+
+ if (mutation.payload) {
+ data.payload = mutation.payload
+ }
+
+ // @ts-ignore
+ api.notifyComponentUpdate()
+ api.sendInspectorState(piniaInspectorId)
+
+ api.addTimelineEvent({
+ layerId: mutationsLayerId,
+ event: {
+ time: Date.now(),
+ data,
+ // TODO: remove when fixed
+ meta: {},
+ },
+ })
+ })
+
+ api.on.getInspectorTree((payload) => {
+ if (payload.app === app && payload.inspectorId === piniaInspectorId) {
+ const stores = Array.from(getRegisteredStores())
+
+ payload.rootNodes = (payload.filter
+ ? stores.filter((store) =>
+ store.id.toLowerCase().includes(payload.filter.toLowerCase())
+ )
+ : stores
+ ).map(formatStoreForInspectorTree)
+ }
+ })
+
+ api.on.getInspectorState((payload) => {
+ if (payload.app === app && payload.inspectorId === piniaInspectorId) {
+ const stores = Array.from(getRegisteredStores())
+ const store = stores.find((store) => store.id === payload.nodeId)
+
+ if (store) {
+ payload.state = {
+ options: formatStoreForInspectorState(store),
+ }
+ } else {
+ __VUE_DEVTOOLS_TOAST__(
+ `🍍 store "${payload.nodeId}" not found`,
+ 'error'
+ )
+ }
+ }
+ })
+
+ // trigger an update so it can display new registered stores
+ // @ts-ignore
+ api.notifyComponentUpdate()
+ __VUE_DEVTOOLS_TOAST__(`🍍 "${store.id}" store installed`)
+ }
+ )
+}
+
+function formatStoreForInspectorTree(store: GenericStore): CustomInspectorNode {
+ return {
+ id: store.id,
+ label: store.id,
+ tags: [],
+ }
+}
+
+function formatStoreForInspectorState(
+ store: GenericStore
+): CustomInspectorState[string] {
+ const fields: CustomInspectorState[string] = [
+ { editable: false, key: 'id', value: formatDisplay(store.id) },
+ { editable: true, key: 'state', value: store.state },
+ ]
+
+ return fields
+}
--- /dev/null
+// Global compile-time constants
+declare var __DEV__: boolean
+declare var __FEATURE_PROD_DEVTOOLS__: boolean
+declare var __BROWSER__: boolean
+declare var __CI__: boolean
+declare var __VUE_DEVTOOLS_TOAST__: (
+ message: string,
+ type?: 'normal' | 'error' | 'warning'
+) => void
-export { createStore } from './store'
-export { setActiveReq, setStateProvider, getRootState } from './rootStore'
+import { createStore } from './store'
+export {
+ setActiveReq,
+ setStateProvider,
+ getRootState,
+ createPinia,
+} from './rootStore'
export { StateTree, StoreGetter, Store } from './types'
+// TODO: deprecate createStore
+export { createStore, createStore as defineStore }
+import { App } from 'vue'
import { NonNullObject, StateTree, GenericStore } from './types'
/**
return rootState
}
+
+/**
+ * Client-side application instance used for devtools
+ */
+export let clientApp: App | undefined
+export const setClientApp = (app: App) => (clientApp = app)
+export const getClientApp = () => clientApp
+
+export function createPinia() {
+ return {
+ install(app: App) {
+ setClientApp(app)
+ },
+ }
+}
+
+/**
+ * Registered stores
+ */
+export const stores = new Set<GenericStore>()
+
+export function registerStore(store: GenericStore) {
+ stores.add(store)
+}
+
+export const getRegisteredStores = () => stores
setActiveReq,
storesMap,
getInitialState,
+ getClientApp,
} from './rootStore'
+import { addDevtools } from './devtools'
+
+const IS_CLIENT = typeof window !== 'undefined'
function innerPatch<T extends StateTree>(
target: T,
(store = buildStore(id, state, getters, actions, getInitialState(id)))
)
- // TODO: client devtools when availables
- // if (isClient) useStoreDevtools(store)
+ if (IS_CLIENT && __BROWSER__ && (__DEV__ || __FEATURE_PROD_DEVTOOLS__)) {
+ const app = getClientApp()
+ if (app) {
+ addDevtools(app, store, req)
+ } else {
+ console.warn(
+ `[🍍]: store was instantiated before calling\n` +
+ `app.use(pinia)\n` +
+ `Make sure to install pinia's plugin by using createPinia:\n` +
+ `linkto docs TODO`
+ )
+ }
+ }
}
return store
{
- "include": ["src/**/*.ts", "__tests__/**/*.ts"],
+ "include": [
+ "src/global.d.ts",
+ "src/**/*.ts",
+ "__tests__/**/*.ts"
+ ],
"compilerOptions": {
"baseUrl": ".",
"rootDir": ".",
"@vue/compiler-core" "3.0.0"
"@vue/shared" "3.0.0"
+"@vue/devtools-api@^6.0.0-beta.2":
+ version "6.0.0-beta.2"
+ resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.0.0-beta.2.tgz#833ad3335f97ae9439e26247d97f9baf7b5a6116"
+ integrity sha512-5k0A8ffjNNukOiceImBdx1e3W5Jbpwqsu7xYHiZVu9mn4rYxFztIt+Q25mOHm7nwvDnMHrE7u5KtY2zmd+81GA==
+
"@vue/reactivity@3.0.0":
version "3.0.0"
resolved "https://registry.yarnpkg.com/@vue/reactivity/-/reactivity-3.0.0.tgz#fd15632a608650ce2a969c721787e27e2c80aa6b"
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
+base64-js@^1.1.2:
+ version "1.3.1"
+ resolved "https://registry.yarnpkg.com/base64-js/-/base64-js-1.3.1.tgz#58ece8cb75dd07e71ed08c736abc5fac4dbf8df1"
+ integrity sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g==
+
base@^0.11.1:
version "0.11.2"
resolved "https://registry.yarnpkg.com/base/-/base-0.11.2.tgz#7bde5ced145b6d551a90db87f83c558b4eb48a8f"
dependencies:
fill-range "^7.0.1"
+brotli@^1.3.2:
+ version "1.3.2"
+ resolved "https://registry.yarnpkg.com/brotli/-/brotli-1.3.2.tgz#525a9cad4fcba96475d7d388f6aecb13eed52f46"
+ integrity sha1-UlqcrU/LqWR119OI9q7LE+7VL0Y=
+ dependencies:
+ base64-js "^1.1.2"
+
browser-process-hrtime@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"