Skip to content

Commit

Permalink
custom store key and add auto key feature via plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardoperra committed Nov 3, 2024
1 parent b563c3d commit b86c154
Show file tree
Hide file tree
Showing 10 changed files with 293 additions and 130 deletions.
2 changes: 1 addition & 1 deletion examples/counter/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,5 @@ import { defineConfig } from 'vite';
import { statebuilder } from 'statebuilder/vite';

export default defineConfig({
plugins: [solid({ ssr: false }), statebuilder({ dev: true })],
plugins: [solid({ ssr: false }), statebuilder({ dev: true, autoKey: true })],
});
2 changes: 2 additions & 0 deletions packages/state/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@
"coverage": "vitest run --coverage"
},
"devDependencies": {
"@babel/types": "^7.26.0",
"@solidjs/testing-library": "^0.5.1",
"@vitest/coverage-c8": "^0.26.3",
"consola": "^3.2.3",
Expand All @@ -110,6 +111,7 @@
"vitest": "^2.1.3"
},
"dependencies": {
"@babel/parser": "^7.26.2",
"@solid-primitives/event-bus": "^1.0.11",
"rxjs": "^7.8.0",
"solid-js": "^1.9.2"
Expand Down
15 changes: 13 additions & 2 deletions packages/state/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ import { StateBuilderError } from '~/error';

export const $CREATOR = Symbol('store-creator-api'),
$PLUGIN = Symbol('store-plugin');

export interface CreateOptions {
key: string;
}

/**
* A factory function that creates a store API definition creator.
*
Expand All @@ -27,8 +32,14 @@ export function create<P extends any[], T extends GenericStoreApi>(
): (...args: P) => ApiDefinitionCreator<T> {
let id = 0;
return (...args) => {
const resolvedName = `${name}-${++id}`;
return new ApiDefinition<T, {}>(resolvedName, id, () => creator(...args));
let options = args.at(-1) as Partial<CreateOptions> | undefined;
let resolvedName = options?.key;
if (!resolvedName) {
resolvedName = `${name}-${++id}`;
}
return new ApiDefinition<T, {}>(resolvedName, id, () =>
creator(...(args as unknown as P)),
);
};
}

Expand Down
10 changes: 6 additions & 4 deletions packages/state/src/dev.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,10 @@ if ($SB_DEV) {
}

export function __devRegisterContainer(container: Container) {
statebuilder.containers.push(container);
console.groupCollapsed(`[StateBuilder] - Register container`);
console.log(containers);
console.groupEnd();
containers.push(container);
if ('window' in globalThis) {
console.groupCollapsed(`[statebuilder] - Register container`);
console.log(containers);
console.groupEnd();
}
}
11 changes: 4 additions & 7 deletions packages/state/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"allowSyntheticDefaultImports": true,
"esModuleInterop": true,
"paths": {
"~/*": [
"./src/*"
]
"~/*": ["./src/*"]
}
},
"include": [
"./src",
"./test"
]
"include": ["./src", "./test"]
}
1 change: 1 addition & 0 deletions packages/state/tsup.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export default defineConfig((options) => {
},
{
entry: ['./vite/index.ts'],
external: ['@babel/types'],
outDir: './dist/vite',
format: 'esm',
platform: 'node',
Expand Down
28 changes: 28 additions & 0 deletions packages/state/vite/autoKey.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Plugin } from 'vite';
import { astAddAutoNaming } from './internal/astAutoNaming';
import { parseModule } from 'magicast';

interface StatebuilderAutonamingOptions {
transformStores: string[];
}
export function autoKey(options: StatebuilderAutonamingOptions): Plugin {
const { transformStores } = options;
return {
name: 'statebuilder:autokey',
transform(code, id, options) {
if (!code.includes('statebuilder')) {
return;
}
const findStoresTransformRegexp = new RegExp(transformStores.join('|'));
if (findStoresTransformRegexp.test(code)) {
const module = parseModule(code);
const result = astAddAutoNaming(module.$ast, (functionName) =>
transformStores.includes(functionName),
);
if (result) {
return module.generate();
}
}
},
};
}
69 changes: 59 additions & 10 deletions packages/state/vite/index.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Plugin } from 'vite';
import { createConsola } from 'consola';
import { ConsolaInstance, createConsola } from 'consola';
import { colors } from 'consola/utils';
import { autoKey } from './autoKey';

export interface StateBuilderPluginOptions {
/**
Expand All @@ -9,30 +10,78 @@ export interface StateBuilderPluginOptions {
* @default false when running `build` command.
*/
dev?: boolean;
/**
* If true, generates the key valued as the variable declaration name.
*/
autoKey?: boolean;
/**
* A set of custom primitives to be included into the plugin transform processor
*/
transformStores: string[];
}

function logPrefix() {
return colors.bgBlueBright(' StateBuilder ');
}

export function statebuilder(options?: StateBuilderPluginOptions): Plugin {
export function statebuilder(options?: StateBuilderPluginOptions): Plugin[] {
const consola = createConsola();

const defaultTransformStores = ['defineStore', 'defineSignal'];
const transformStores = [
...defaultTransformStores,
...(options?.transformStores ?? []),
];

let isDev: boolean = false;
const plugins: Plugin[] = [];

return {
name: 'statebuilder-config',
plugins.push({
name: 'statebuilder:config',
enforce: 'pre',
config(userConfig, { command }) {
isDev = options?.dev ?? command === 'serve';

const plugins: Plugin[] = [];
if (options?.autoKey) {
plugins.push(autoKey({ transformStores }));
}

return {
define: {
__STATEBUILDER_DEV__: isDev,
},
plugins,
};
},
configResolved() {
consola.log('\n');
consola.log(`${logPrefix()}`);
consola.log(` ${colors.magenta(`mode`)} ${isDev ? 'DEV' : 'PROD'}`);
logProperties(consola, [
['mode', isDev ? 'DEV' : 'PROD', colors.magenta],
['autoKey', options?.autoKey ?? false, colors.blue],
]);
},
};
});

if (options?.autoKey) {
plugins.push(autoKey({ transformStores }));
}

return plugins;
}

function logPrefix() {
return colors.bgBlueBright(' StateBuilder ');
}

function logProperties(
consola: ConsolaInstance,
entries: ReadonlyArray<
[key: string, value: any, modifier: (s: any) => string]
>,
) {
const lenghts = entries.map(([key]) => key.length);
const maxLength = Math.max(...lenghts);
const padding = maxLength + 2;
for (const [key, value, modifier] of entries) {
const space = new Array(padding - key.length).fill(' ');
consola.log(`${modifier(key)}${space.join('')}`, value);
}
}
42 changes: 42 additions & 0 deletions packages/state/vite/internal/astAutoNaming.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import * as t from '@babel/types';

export function astAddAutoNaming(
program: t.Node,
filterStores: (name: string) => boolean,
): boolean {
if (program.type !== 'Program') {
return false;
}
let modify = false;
for (const statement of program.body) {
t.traverse(statement, (node, ancestors) => {
if (t.isCallExpression(node)) {
if (t.isIdentifier(node.callee) && filterStores(node.callee.name)) {
ancestorsLoop: {
let variableName: string | null = null;
for (const ancestor of ancestors) {
if (
t.isVariableDeclarator(ancestor.node) &&
t.isIdentifier(ancestor.node.id)
) {
variableName = ancestor.node.id.name;
modify = true;
const storeOptions = t.objectExpression([
createNameProperty(variableName!),
]);
node.arguments.push(storeOptions);
break ancestorsLoop;
}
}
}
}
}
});
}

return modify;
}

function createNameProperty(variableName: string) {
return t.objectProperty(t.identifier('key'), t.stringLiteral(variableName));
}
Loading

0 comments on commit b86c154

Please sign in to comment.