diff --git a/README.md b/README.md
index fe68c0e..ba10c48 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,9 @@
-> **Warning** This library has been built for experimental purposes for my needs while building apps that need an
+> [!WARNING]
+>
+> This library has been built for experimental purposes for my needs while building apps that need an
> agnostic state manager and a certain complexity.
[![NPM](https://img.shields.io/npm/v/statebuilder?style=for-the-badge)](https://www.npmjs.com/package/statebuilder)
@@ -10,13 +12,12 @@
`StateBuilder` is an agnostic state management library built on the top of SolidJS reactivity.
It's built to be an **extremely modular system**, with an API that allows you to add methods, utilities and custom
-behaviors to your store
-in an easier way. Of course, this come with a built-in TypeScript support.
+behaviors to your store in an easier way. Of course, this come with a built-in TypeScript support.
Solid already provides the primitives to build a state manager system thanks to signals and stores. What's missing is a
well-defined pattern to follow while building your application.
-Thanks to `StateBuilder` you can **compose** the approach you like to handle your state.
+Thanks to `StateBuilder` you can **compose** the approach you like to handle your state. You can also use some patterns already exposed by the library.
## Table of contents
@@ -27,12 +28,10 @@ Thanks to `StateBuilder` you can **compose** the approach you like to handle you
## Architecture
-`StateBuilder` come to the rescue introducing some concepts:
-
### **State container**
The state container it's a plain JavaScript object that collects all resolved store instances. Once created, every state
-container will have it's own reactive scope, defined by the `Owner` object from solid-js API.
+container will have it's own reactive scope, bound into a `Owner` from solid-js API.
### **Store definition creator**
@@ -44,7 +43,7 @@ specific signature to be complaint to `StateBuilder` API.
- defineStore
- defineSignal
-Using the store definition creator, you can define your state business logic, which will be
+Using the store definition, you can define your state business logic, which will be
**lazy evaluated** once the state is injected the first time.
### **Plugin**
@@ -74,9 +73,17 @@ Install `StateBuilder` by running the following command of the following:
pnpm i statebuilder # or npm or yarn
```
-### Enable compiler / plugin (Vite Only)
+### Enable compiler (Vite Only)
-If you're using Vite with SolidJS, you can use the `statebuilder` custom plugin, which allows to debug easily in dev.
+> [!NOTE]
+>
+> The statebuilder plugin is OPTIONAL. This means that all the core features works right out of the
+> box without a custom build step
+
+If you're using Vite with SolidJS, you can use the `statebuilder` custom plugin, which provide debug and custom features through babel transforms.
+
+- `autoKey`: Allows to name your stores automatically, based on the constant name.
+- `stateProviderDirective`: Allows to wraps your SolidJS component into a StateProvider when they contains the `use stateprovider` directive.
```ts
import { defineConfig } from 'vite';
@@ -87,6 +94,12 @@ export default defineConfig({
plugins: [
statebuilder({
autoKey: true,
+ filterStores: [
+ // define your custom store name
+ ]
+ experimental: {
+ transformStateProviderDirective: true
+ }
}),
],
});
diff --git a/packages/state/vite/autoKey.ts b/packages/state/vite/autoKey.ts
index 382863c..d36f62f 100644
--- a/packages/state/vite/autoKey.ts
+++ b/packages/state/vite/autoKey.ts
@@ -1,28 +1,45 @@
+import * as babel from '@babel/core';
+import { basename } from 'node:path';
import { Plugin } from 'vite';
-import { astAddAutoNaming } from './babel/astAutoNaming';
-import { parseModule } from 'magicast';
+import {
+ babelAstAddAutoNaming,
+ BabelAstAddAutoNamingOptions,
+} from './babel/astAutoNaming';
+import { transformAsync } from './babel/transform';
interface StatebuilderAutonamingOptions {
transformStores: string[];
}
export function autoKey(options: StatebuilderAutonamingOptions): Plugin {
const { transformStores } = options;
+ const findStoresTransformRegexp = new RegExp(transformStores.join('|'));
return {
name: 'statebuilder:autokey',
- transform(code, id, options) {
- if (!code.includes('statebuilder')) {
+ async transform(code, id, options) {
+ if (
+ !code.includes('statebuilder') ||
+ !findStoresTransformRegexp.test(code)
+ ) {
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();
- }
+ const result = await transformAsync(id, code, [
+ [
+ babelAstAddAutoNaming,
+ {
+ filterStores: (functionName) =>
+ transformStores.includes(functionName),
+ } as BabelAstAddAutoNamingOptions,
+ ],
+ ]);
+
+ if (!result) {
+ return;
}
+
+ return {
+ code: result.code ?? '',
+ map: result.map,
+ };
},
};
}
diff --git a/packages/state/vite/babel/astAutoNaming.ts b/packages/state/vite/babel/astAutoNaming.ts
index 1469af9..84d9f63 100644
--- a/packages/state/vite/babel/astAutoNaming.ts
+++ b/packages/state/vite/babel/astAutoNaming.ts
@@ -1,5 +1,43 @@
import * as t from '@babel/types';
+export interface BabelAstAddAutoNamingOptions {
+ filterStores: (callee: string) => boolean;
+}
+
+export function babelAstAddAutoNaming({
+ filterStores,
+}: BabelAstAddAutoNamingOptions): babel.PluginObj {
+ return {
+ name: 'statebuilder:stateprovider-addAutoName',
+ visitor: {
+ CallExpression(path) {
+ if (
+ t.isIdentifier(path.node.callee) &&
+ filterStores(path.node.callee.name)
+ ) {
+ const ancestors = path.getAncestry();
+ 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;
+ const storeOptions = t.objectExpression([
+ createNameProperty(variableName!),
+ ]);
+ path.node.arguments.push(storeOptions);
+ break ancestorsLoop;
+ }
+ }
+ }
+ }
+ },
+ },
+ };
+}
+
export function astAddAutoNaming(
program: t.Node,
filterStores: (name: string) => boolean,
@@ -7,6 +45,7 @@ export function astAddAutoNaming(
if (program.type !== 'Program') {
return false;
}
+
let modify = false;
for (const statement of program.body) {
t.traverse(statement, (node, ancestors) => {
diff --git a/packages/state/vite/babel/transform.ts b/packages/state/vite/babel/transform.ts
new file mode 100644
index 0000000..0473050
--- /dev/null
+++ b/packages/state/vite/babel/transform.ts
@@ -0,0 +1,27 @@
+import * as babel from '@babel/core';
+import { basename } from 'node:path';
+
+export function transformAsync(
+ moduleId: string,
+ code: string,
+ customPlugins: babel.TransformOptions['plugins'],
+) {
+ const plugins: NonNullable<
+ NonNullable['plugins']
+ > = ['jsx'];
+ if (/\.[mc]?tsx?$/i.test(moduleId)) {
+ plugins.push('typescript');
+ }
+ return babel.transformAsync(code, {
+ plugins: customPlugins,
+ parserOpts: {
+ plugins,
+ },
+ filename: basename(moduleId),
+ ast: false,
+ sourceMaps: true,
+ configFile: false,
+ babelrc: false,
+ sourceFileName: moduleId,
+ });
+}
diff --git a/packages/state/vite/stateProviderDirective.ts b/packages/state/vite/stateProviderDirective.ts
index 39827fd..cc444a0 100644
--- a/packages/state/vite/stateProviderDirective.ts
+++ b/packages/state/vite/stateProviderDirective.ts
@@ -2,6 +2,7 @@ import * as babel from '@babel/core';
import { basename } from 'node:path';
import { Plugin } from 'vite';
import { babelReplaceStateProviderDirective } from './babel/replaceStateProviderDirective';
+import { transformAsync } from './babel/transform';
export function stateProviderDirective(): Plugin {
return {
@@ -11,26 +12,9 @@ export function stateProviderDirective(): Plugin {
if (code.indexOf('use stateprovider') === -1) {
return;
}
- const plugins: NonNullable<
- NonNullable['plugins']
- > = ['jsx'];
- if (/\.[mc]?tsx?$/i.test(id)) {
- plugins.push('typescript');
- }
-
- const result = await babel.transformAsync(code, {
- plugins: [[babelReplaceStateProviderDirective]],
- parserOpts: {
- plugins,
- },
- filename: basename(id),
- ast: false,
- sourceMaps: true,
- configFile: false,
- babelrc: false,
- sourceFileName: id,
- });
-
+ const result = await transformAsync(id, code, [
+ [babelReplaceStateProviderDirective],
+ ]);
if (result) {
return {
code: result.code || '',
diff --git a/packages/state/vitest.config.ts b/packages/state/vitest.config.ts
index f07eb7a..aa30506 100644
--- a/packages/state/vitest.config.ts
+++ b/packages/state/vitest.config.ts
@@ -14,5 +14,5 @@ export default defineConfig({
// threads: false,
// isolate: false,
},
- plugins: [solidPlugin(), tsconfigPaths(), statebuilder()],
+ plugins: [solidPlugin(), tsconfigPaths()],
});