@flamework-experimental/core 2.0.0-alpha.3 → 2.0.0-alpha.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -6
- package/docs/README.md +63 -0
- package/docs/guide/01-getting-started.md +334 -0
- package/docs/guide/02-modules.md +254 -0
- package/docs/guide/03-providers.md +423 -0
- package/docs/guide/04-lifecycle-events.md +423 -0
- package/docs/guide/05-components.md +793 -0
- package/docs/guide/06-networking.md +614 -0
- package/docs/guide/07-macros.md +332 -0
- package/docs/guide/08-plugins.md +203 -0
- package/docs/guide/09-project-structure.md +392 -0
- package/docs/guide/10-migrating-from-v1.md +573 -0
- package/docs/guide/11-scopes.md +165 -0
- package/docs/guide/12-testing.md +342 -0
- package/flamework.build +1 -1
- package/out/index.d.ts +1 -0
- package/out/init.luau +1 -0
- package/out/module/module.luau +1 -1
- package/out/module/moduleBuilder.luau +1 -1
- package/out/utility/getClassesInPath.d.ts +27 -1
- package/out/utility/getClassesInPath.luau +101 -19
- package/out/utility/pathRoot.d.ts +20 -1
- package/out/utility/pathRoot.luau +80 -4
- package/package.json +14 -7
package/README.md
CHANGED
|
@@ -12,8 +12,13 @@ material:
|
|
|
12
12
|
| | |
|
|
13
13
|
|---|---|
|
|
14
14
|
| [Guide](docs/README.md#guide) | Getting started, modules, providers, lifecycle events, components, networking, macros, plugins, project structure, migrating from v1, scopes, testing in the place. |
|
|
15
|
-
| [Internals](docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
|
|
16
|
-
| [Transformer plugins](docs/reference/transformer-plugins.md) | Adding macro types of your own. |
|
|
15
|
+
| [Internals](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/reference/internals.md) | What the transformer does to your code and what the runtime does with the result. |
|
|
16
|
+
| [Transformer plugins](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/reference/transformer-plugins.md) | Adding macro types of your own. |
|
|
17
|
+
|
|
18
|
+
The guide also ships inside the core package, for the version you installed:
|
|
19
|
+
`node_modules/@flamework-experimental/core/docs/README.md` is its index, and the pages are in
|
|
20
|
+
`node_modules/@flamework-experimental/core/docs/guide/`. Instructions for a coding assistant can
|
|
21
|
+
point there, so that it reads the docs for your version.
|
|
17
22
|
|
|
18
23
|
The Flamework website documents v1, most of which no longer applies:
|
|
19
24
|
|
|
@@ -51,7 +56,7 @@ bun run test:place # the in-place suite in Roblox Studio (tests/place); need
|
|
|
51
56
|
- **Transformer tests** (`bun run test:unit`) build a fixture project with the real `rbxtsc` and
|
|
52
57
|
check the Luau it emits: guard generation, identifiers, nested macros and the plugin system.
|
|
53
58
|
- **Runtime specs** (`bun run test:runtime`) run the built `@flamework-experimental/core`,
|
|
54
|
-
`components` and `networking` packages under Lune. The harness in [`tests/runtime`](tests/runtime)
|
|
59
|
+
`components` and `networking` packages under Lune. The harness in [`tests/runtime`](https://github.com/Velover/ExperimentalFlameworkV2/tree/HEAD/tests/runtime)
|
|
55
60
|
models roblox-ts's `TS.import` tree over the filesystem. It also stubs the parts of the Roblox API
|
|
56
61
|
that Flamework uses: Instances, attributes, CollectionService, RemoteEvents, Players, signals,
|
|
57
62
|
`task`, `Enum`, and a `Heartbeat` pump so that `Promise.delay` runs (and with it, request
|
|
@@ -65,10 +70,10 @@ bun run test:place # the in-place suite in Roblox Studio (tests/place); need
|
|
|
65
70
|
example, from the server a function receives on `$name` and sends on `@name`, and from the client
|
|
66
71
|
it does the reverse, so the two runs pin down the wire format from both ends.
|
|
67
72
|
|
|
68
|
-
Specs live in [`packages/specs`](packages/specs). `rbxtsc` builds them like any other project that
|
|
73
|
+
Specs live in [`packages/specs`](https://github.com/Velover/ExperimentalFlameworkV2/tree/HEAD/packages/specs). `rbxtsc` builds them like any other project that
|
|
69
74
|
uses Flamework, so they test the transformer and the runtime together.
|
|
70
75
|
|
|
71
76
|
A third suite runs against the real engine, and `bun run test` leaves it out. It lives in the
|
|
72
|
-
[test place](tests/place/README.md), a small game linked to the packages' builds.
|
|
77
|
+
[test place](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/tests/place/README.md), a small game linked to the packages' builds.
|
|
73
78
|
`bun run test:place` runs its `@flamework-experimental/testing` sections in Roblox Studio, on both
|
|
74
|
-
realms, under four Rojo projects (see [Testing in Roblox Studio](docs/testing/studio.md)).
|
|
79
|
+
realms, under four Rojo projects (see [Testing in Roblox Studio](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/testing/studio.md)).
|
package/docs/README.md
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# Flamework documentation
|
|
2
|
+
|
|
3
|
+
Flamework is a framework for roblox-ts built around **modules**. A module is a dependency-injection
|
|
4
|
+
container that you create and start yourself; starting it is called *igniting* it. Everything else
|
|
5
|
+
is a plugin or a package built on top of modules: lifecycle events, components and networking.
|
|
6
|
+
|
|
7
|
+
> These docs cover **v2**, which is still in alpha: its packages are published as `2.0.0-alpha`
|
|
8
|
+
> prereleases. The
|
|
9
|
+
> [Flamework website](https://flamework.fireboltofdeath.dev/docs/introduction) documents v1, and
|
|
10
|
+
> most of it no longer applies. See [migrating from v1](guide/10-migrating-from-v1.md).
|
|
11
|
+
|
|
12
|
+
## Guide
|
|
13
|
+
|
|
14
|
+
Read the pages in order the first time. Most pages have a **Caveats** section for their topic, near
|
|
15
|
+
the end.
|
|
16
|
+
|
|
17
|
+
| | Page | Covers |
|
|
18
|
+
|---|---|---|
|
|
19
|
+
| 1 | [Getting started](guide/01-getting-started.md) | Install, `tsconfig`, Rojo, your first working module on both realms. |
|
|
20
|
+
| 2 | [Modules](guide/02-modules.md) | What a module is, igniting and extinguishing it, `Dependency<T>()`, importing one module into another. |
|
|
21
|
+
| 3 | [Providers](guide/03-providers.md) | `@Provider`, automatic registration, dependency injection, `@Injectable`. |
|
|
22
|
+
| 4 | [Lifecycle events](guide/04-lifecycle-events.md) | `OnStart`, `OnTick` and the other events, and listeners you attach by hand. |
|
|
23
|
+
| 5 | [Components](guide/05-components.md) | Classes attached to instances: attributes, guards, streaming, dependencies. |
|
|
24
|
+
| 6 | [Networking](guide/06-networking.md) | Events, functions, namespaces, unreliable channels, middleware. |
|
|
25
|
+
| 7 | [Macros](guide/07-macros.md) | What the transformer fills in, and writing macros of your own. |
|
|
26
|
+
| 8 | [Plugins](guide/08-plugins.md) | Extending Flamework itself with hooks and interfaces. |
|
|
27
|
+
| 9 | [Project structure](guide/09-project-structure.md) | Folder layout, one module or several, testing. |
|
|
28
|
+
| 10 | [Migrating from v1](guide/10-migrating-from-v1.md) | What changed, and what to do about it. |
|
|
29
|
+
| 11 | [Scopes](guide/11-scopes.md) | Build scopes from `.env`: test scenarios, debug tools and stand-ins that exist only in the builds that ask for them. |
|
|
30
|
+
| 12 | [Testing in the place](guide/12-testing.md) | Sections of tests that run inside a real place through a bindable, a remote or a cloud task, with cleanup that always runs. |
|
|
31
|
+
|
|
32
|
+
## Reference
|
|
33
|
+
|
|
34
|
+
- [Internals](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/reference/internals.md) -- what the transformer does to your code, and what the
|
|
35
|
+
runtime does with the result. Read this to change Flamework, or to debug something that only fails
|
|
36
|
+
at runtime.
|
|
37
|
+
- [Transformer plugins](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/reference/transformer-plugins.md) -- adding macro types of your own.
|
|
38
|
+
- [Future considerations](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/future-considerations.md) -- possible future directions, and the known
|
|
39
|
+
limits that were left alone. It covers the estimated cost of each feature, why Immediate signal
|
|
40
|
+
behaviour stays supported, and simpler rules for rare features.
|
|
41
|
+
|
|
42
|
+
## Testing
|
|
43
|
+
|
|
44
|
+
- [Testing in Roblox Studio](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/testing/studio.md) -- the battletest, which runs a real place on the
|
|
45
|
+
packages. It covers setup, the automated matrix, the manual scenarios, and what the first run
|
|
46
|
+
found.
|
|
47
|
+
- [Running the tests](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/docs/testing/place.md) -- `flamework-test`, run from a terminal. It opens the build
|
|
48
|
+
in Roblox Studio and runs it on both realms, or publishes it and runs it in a real server through
|
|
49
|
+
Open Cloud.
|
|
50
|
+
- [The test place](https://github.com/Velover/ExperimentalFlameworkV2/blob/HEAD/tests/place/README.md) -- the place that this repository's in-place suite runs
|
|
51
|
+
in. It is linked to the packages' own builds. `bun run test:place` builds the packages and runs the
|
|
52
|
+
suite in Studio under four Rojo projects (default, immediate, deferred, streaming).
|
|
53
|
+
|
|
54
|
+
## I just want to…
|
|
55
|
+
|
|
56
|
+
| | |
|
|
57
|
+
|---|---|
|
|
58
|
+
| …get a provider (a v1 service) running | [Getting started](guide/01-getting-started.md), then [Providers](guide/03-providers.md) |
|
|
59
|
+
| …run code every frame | [Lifecycle events](guide/04-lifecycle-events.md) |
|
|
60
|
+
| …attach a class to an Instance | [Components](guide/05-components.md) |
|
|
61
|
+
| …send something to the server | [Networking](guide/06-networking.md) |
|
|
62
|
+
| …understand why an argument is `nil` | [Macros › when a macro does not fire](guide/07-macros.md#when-a-macro-does-not-fire) |
|
|
63
|
+
| …know what an error means | the **Caveats** section of the guide page for that topic lists them (pages 1–9 and 11 have one) |
|
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
# 1. Getting started
|
|
2
|
+
|
|
3
|
+
By the end of this page you will have a provider (what v1 called a service or a controller) running
|
|
4
|
+
on the server and on the client. Flamework finds and registers it for you, so there is no wiring to
|
|
5
|
+
write by hand.
|
|
6
|
+
|
|
7
|
+
## Install
|
|
8
|
+
|
|
9
|
+
```sh
|
|
10
|
+
npm install @flamework-experimental/core
|
|
11
|
+
npm install -D @flamework-experimental/transformer
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The commands in these guides use npm. bun (`bun add`, `bun add -d`) and pnpm (`pnpm add`,
|
|
15
|
+
`pnpm add -D`) work the same way.
|
|
16
|
+
|
|
17
|
+
**Pin TypeScript to the version roblox-ts compiles with.** roblox-ts depends on one exact
|
|
18
|
+
TypeScript (roblox-ts 3.0.0 on 5.5.3), but `create-roblox-ts` installs `typescript` unpinned, which
|
|
19
|
+
gives the latest release (7.x at the time of writing). The build still works, since Flamework
|
|
20
|
+
switches to roblox-ts's TypeScript, but every build prints `TypeScript version differs`, and your
|
|
21
|
+
editor checks the code with another compiler than the build. So install the same version:
|
|
22
|
+
|
|
23
|
+
```sh
|
|
24
|
+
npm install -D --save-exact typescript@5.5.3
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
With another roblox-ts, take the version from the `typescript` entry of the `dependencies` in
|
|
28
|
+
`node_modules/roblox-ts/package.json`.
|
|
29
|
+
|
|
30
|
+
Add the transformer to `tsconfig.json`. **Nothing in these docs works without it.** Most of
|
|
31
|
+
Flamework's API is made of *macros*: functions with some arguments that the transformer fills in
|
|
32
|
+
when you build. Without the transformer, those arguments are missing at runtime.
|
|
33
|
+
|
|
34
|
+
```jsonc
|
|
35
|
+
{
|
|
36
|
+
"compilerOptions": {
|
|
37
|
+
"plugins": [
|
|
38
|
+
{
|
|
39
|
+
"transform": "@flamework-experimental/transformer"
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Also add the `@flamework-experimental` npm scope to `typeRoots`, next to `@rbxts`. roblox-ts only
|
|
47
|
+
accepts imports from the scopes listed there:
|
|
48
|
+
|
|
49
|
+
```jsonc
|
|
50
|
+
"typeRoots": ["node_modules/@rbxts", "node_modules/@flamework-experimental"]
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
(TypeScript treats every package under a `typeRoots` directory as a type library. That is why the
|
|
54
|
+
transformer and the CLI ship an empty declaration file.)
|
|
55
|
+
|
|
56
|
+
That is all the configuration you need. Optional settings go in a `flamework.config.json` next to
|
|
57
|
+
`tsconfig.json`, with one section per package. They never go on the tsconfig entry: the build fails
|
|
58
|
+
if the entry sets one. See [Project structure](09-project-structure.md#configuration).
|
|
59
|
+
|
|
60
|
+
You don't have to write that file to see what you can set. With `tsconfig.json` at the root of your
|
|
61
|
+
package, as here, your first build creates it, holding just a `$schema` line. It also adds the line
|
|
62
|
+
to a file that lacks it. With that line, your editor lists every option with its description and
|
|
63
|
+
its default. Commit the file. The build also writes `flamework.build` and `include/flamework/`,
|
|
64
|
+
which you do not commit; see [Project structure › What to commit](09-project-structure.md#what-to-commit).
|
|
65
|
+
|
|
66
|
+
### Incremental builds and upgrades
|
|
67
|
+
|
|
68
|
+
The `create-roblox-ts` template turns on `"incremental": true`, with
|
|
69
|
+
`"tsBuildInfoFile": "out/tsconfig.tsbuildinfo"`. An incremental build recompiles only the files
|
|
70
|
+
that changed since the last one. After you upgrade Flamework, the other files would keep what the
|
|
71
|
+
old version emitted, so the first build stops:
|
|
72
|
+
|
|
73
|
+
```
|
|
74
|
+
[Flamework]: Project was compiled on different version of Flamework.
|
|
75
|
+
[Flamework]: This is an incremental build, which recompiles only the files that changed. Delete out/tsconfig.tsbuildinfo and build again: the next build compiles every file.
|
|
76
|
+
[Flamework]: Current Flamework Version: 2.0.0-alpha.5
|
|
77
|
+
[Flamework]: Previous Flamework Version: 2.0.0-alpha.4
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Delete the file it names and build again. roblox-ts chooses the files to compile before it loads the
|
|
81
|
+
transformer, so the build cannot start over by itself. `incremental` without a `tsBuildInfoFile`
|
|
82
|
+
builds incrementally too, into TypeScript's default tsbuildinfo, and behaves the same. Where that
|
|
83
|
+
file goes depends on `rootDir`: with the template's `"rootDir": "src"` it is `tsconfig.tsbuildinfo`
|
|
84
|
+
beside `tsconfig.json`, and the message names it either way. To avoid this step on every upgrade,
|
|
85
|
+
remove the two options from `tsconfig.json`: every build is then a full one.
|
|
86
|
+
|
|
87
|
+
## Rojo
|
|
88
|
+
|
|
89
|
+
Your Rojo project file has to map two things: the folders you register from, and the packages.
|
|
90
|
+
|
|
91
|
+
When you build, Flamework turns each source folder you register from into a **Rojo path**: the
|
|
92
|
+
instance path the folder ends up at, such as `ServerScriptService/TS/services`. So the folders you
|
|
93
|
+
register from have to be in the project file. A default roblox-ts project maps all of `out/`, which
|
|
94
|
+
covers them.
|
|
95
|
+
|
|
96
|
+
The packages are ModuleScripts like any `@rbxts` package. They live next to the `@rbxts` packages,
|
|
97
|
+
under `ReplicatedStorage.rbxts_include.node_modules`. Map the whole `@flamework-experimental` folder
|
|
98
|
+
there, in one line:
|
|
99
|
+
|
|
100
|
+
```json
|
|
101
|
+
{
|
|
102
|
+
"name": "my-game",
|
|
103
|
+
"globIgnorePaths": ["**/package.json", "**/tsconfig.json"],
|
|
104
|
+
"tree": {
|
|
105
|
+
"$className": "DataModel",
|
|
106
|
+
"ServerScriptService": {
|
|
107
|
+
"TS": { "$path": "out/server" }
|
|
108
|
+
},
|
|
109
|
+
"ReplicatedStorage": {
|
|
110
|
+
"rbxts_include": {
|
|
111
|
+
"$path": "include",
|
|
112
|
+
"node_modules": {
|
|
113
|
+
"$className": "Folder",
|
|
114
|
+
"@rbxts": { "$path": "node_modules/@rbxts" },
|
|
115
|
+
"@flamework-experimental": { "$path": "node_modules/@flamework-experimental" }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"TS": { "$path": "out/shared" }
|
|
119
|
+
},
|
|
120
|
+
"StarterPlayer": {
|
|
121
|
+
"StarterPlayerScripts": {
|
|
122
|
+
"TS": { "$path": "out/client" }
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Each package you install arrives in the place with its `out` folder: `core`, and `components`,
|
|
130
|
+
`networking` and `testing` when you install them. `core` also ships this guide, as Markdown, which
|
|
131
|
+
Rojo skips; its two folders arrive as empty Folders, `core.docs` and `core.docs.guide`. `testing`
|
|
132
|
+
also ships the sources of its CLI, and a `default.project.json` that maps its `out` folder alone, so
|
|
133
|
+
they stay out of the place (from 2.0.0-alpha.4; an older one brings three empty Folders,
|
|
134
|
+
`testing.cli`, `cli.src` and `cli.tasks`). Rojo uses a package's `default.project.json` in place of
|
|
135
|
+
its folder. The transformer is installed in the same folder, and it arrives as one empty Folder: it
|
|
136
|
+
ships a `default.project.json` too. Two entries are the same as in the roblox-ts template:
|
|
137
|
+
|
|
138
|
+
- `include` holds the roblox-ts runtime and the files Flamework generates when you build
|
|
139
|
+
(`include/flamework`).
|
|
140
|
+
- `globIgnorePaths` stops each package's `package.json` from becoming a ModuleScript.
|
|
141
|
+
|
|
142
|
+
**The one line needs `@flamework-experimental/transformer` 2.0.0-alpha.5 or later.** An older
|
|
143
|
+
transformer ships no `default.project.json`, so Rojo would copy it into `ReplicatedStorage`, which
|
|
144
|
+
replicates to every client. Rojo skips its JavaScript, but its folders arrive as empty Folders and
|
|
145
|
+
its three JSON schemas arrive as ModuleScripts (`flamework-schema`, `flamework.config.schema`,
|
|
146
|
+
`rojo-schema`). With an older transformer, map each runtime package by name instead. This form works
|
|
147
|
+
with every version:
|
|
148
|
+
|
|
149
|
+
```json
|
|
150
|
+
"@flamework-experimental": {
|
|
151
|
+
"$className": "Folder",
|
|
152
|
+
"core": { "$path": "node_modules/@flamework-experimental/core" },
|
|
153
|
+
"components": { "$path": "node_modules/@flamework-experimental/components" },
|
|
154
|
+
"networking": { "$path": "node_modules/@flamework-experimental/networking" }
|
|
155
|
+
}
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
`core` is always needed. Add `components`, `networking` and `testing` when you install them, and
|
|
159
|
+
leave out the ones you do not use.
|
|
160
|
+
|
|
161
|
+
If a folder you register from is not in the project file, the build fails with `Could not find Rojo
|
|
162
|
+
data for 'src/...'`. If a package your code imports is not mapped, roblox-ts fails the build with
|
|
163
|
+
`Could not find Rojo data. There is no $path in your Rojo config that covers ...`, naming a file of
|
|
164
|
+
that package.
|
|
165
|
+
|
|
166
|
+
A folder you register from also has to exist, spelled exactly as it is on disk, case included, and
|
|
167
|
+
hold at least one module:
|
|
168
|
+
|
|
169
|
+
- **A path that names nothing** (a missing folder, a typo, a name that differs in case from the one
|
|
170
|
+
on disk, which is the name the place gets) is not in the place, and the call that names it waits
|
|
171
|
+
for it at runtime. `requireModules` raises instead, once it has waited five seconds.
|
|
172
|
+
- **A folder with no module** (an empty one, or one of declarations only) is still copied into
|
|
173
|
+
`out/`, so the place gets it empty, and the call registers or finds nothing there. Git keeps no
|
|
174
|
+
empty folder, though: in a fresh clone an empty folder is not in the place at all, and the call
|
|
175
|
+
waits for it.
|
|
176
|
+
|
|
177
|
+
The build warns about each such path where it is written, naming the call and the path:
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
[Flamework]: src/server/runtime.server.ts:6:3 - registerProviders("src/shared/nothing-here"): there is no such file or folder, so the place will not have it, and the call waits for it at runtime
|
|
181
|
+
[Flamework]: src/server/runtime.server.ts:7:3 - registerProviders("src/server/Services"): there is no such file or folder; on disk it is 'src/server/services', and the place names it as the disk does, so the call waits at runtime for a name the place does not have
|
|
182
|
+
[Flamework]: src/client/runtime.client.ts:5:44 - ComponentPlugin.fromPath("src/shared/components"): nothing in that folder compiles to a module, so the call finds nothing there, and in a place without the folder (git keeps no empty folder) it waits for it at runtime
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The check follows your Rojo project, as the place does: a folder that a `$path` of its own maps
|
|
186
|
+
inside an `out` folder counts, and so do Luau, JSON, TOML and YAML modules. It covers every path
|
|
187
|
+
macro: `registerProviders`, `ComponentPlugin.fromPath`, `registerComponents`, `requireModules`,
|
|
188
|
+
and a macro of your own. At runtime, a registration keeps waiting for its folder, since a client may
|
|
189
|
+
still be receiving the place. After five seconds it warns once, naming itself:
|
|
190
|
+
`registerProviders("src/shared/nothing-here") is still waiting for its folder: the build put it at
|
|
191
|
+
ReplicatedStorage/TS/nothing-here, and ReplicatedStorage.TS has no child named 'nothing-here' after
|
|
192
|
+
5 seconds. ...`. So register only the folders you have: in the layout of
|
|
193
|
+
[Project structure](09-project-structure.md#a-layout-that-scales), leave out
|
|
194
|
+
`src/shared/components` until it holds a component.
|
|
195
|
+
|
|
196
|
+
## Your first provider
|
|
197
|
+
|
|
198
|
+
A **provider** is usually a class that Flamework creates once per module: a singleton. Mark it with
|
|
199
|
+
`@Provider()` and export it:
|
|
200
|
+
|
|
201
|
+
```ts
|
|
202
|
+
// src/server/services/greeter.ts
|
|
203
|
+
import { OnStart, Provider } from "@flamework-experimental/core";
|
|
204
|
+
|
|
205
|
+
@Provider()
|
|
206
|
+
export class Greeter implements OnStart {
|
|
207
|
+
public onStart() {
|
|
208
|
+
print("hello from the server");
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
## Igniting
|
|
214
|
+
|
|
215
|
+
Your entry point creates a **module**, registers your providers in it, and **ignites** it, which
|
|
216
|
+
starts everything:
|
|
217
|
+
|
|
218
|
+
```ts
|
|
219
|
+
// src/server/runtime.server.ts
|
|
220
|
+
import { Flamework } from "@flamework-experimental/core";
|
|
221
|
+
|
|
222
|
+
Flamework.createModule()
|
|
223
|
+
.registerProviders("src/server/services")
|
|
224
|
+
.ignite();
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Two calls do the work:
|
|
228
|
+
|
|
229
|
+
1. **`registerProviders("src/server/services")`** finds every `@Provider()` class defined in the
|
|
230
|
+
files under that folder, exported or not. You do not list them by hand. See
|
|
231
|
+
[Providers](03-providers.md#registration) for how this works and when it does not.
|
|
232
|
+
2. **`ignite()`** constructs every provider, injects their dependencies, runs every `onInit` in
|
|
233
|
+
dependency order, and then runs `onStart`.
|
|
234
|
+
|
|
235
|
+
`onStart` runs because every module includes `LifecyclePlugin` from the start. A **plugin** adds
|
|
236
|
+
features to a module. `LifecyclePlugin` is an ordinary one: `disableDefaultLifecycle()` on the
|
|
237
|
+
builder leaves it out, and including one built with `createLifecyclePlugin({ … })` replaces it. See
|
|
238
|
+
[Lifecycle events](04-lifecycle-events.md).
|
|
239
|
+
|
|
240
|
+
The client entry point looks the same:
|
|
241
|
+
|
|
242
|
+
```ts
|
|
243
|
+
// src/client/runtime.client.ts
|
|
244
|
+
import { Flamework } from "@flamework-experimental/core";
|
|
245
|
+
|
|
246
|
+
Flamework.createModule()
|
|
247
|
+
.registerProviders("src/client/controllers")
|
|
248
|
+
.ignite();
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
There is no `@Service` / `@Controller` split in v2. The module that registers a provider decides
|
|
252
|
+
which realm gets it. That is why the two entry points register different folders.
|
|
253
|
+
|
|
254
|
+
## Adding a dependency
|
|
255
|
+
|
|
256
|
+
Flamework resolves constructor parameters by their type: each parameter gets what the module
|
|
257
|
+
provides for that type, usually another provider. There are no tokens, no strings and no decorators
|
|
258
|
+
on the parameters:
|
|
259
|
+
|
|
260
|
+
```ts
|
|
261
|
+
// src/server/services/economy.ts
|
|
262
|
+
@Provider()
|
|
263
|
+
export class Economy {
|
|
264
|
+
public balance = 0;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// src/server/services/shop.ts
|
|
268
|
+
@Provider()
|
|
269
|
+
export class Shop implements OnStart {
|
|
270
|
+
constructor(private economy: Economy) {}
|
|
271
|
+
|
|
272
|
+
public onStart() {
|
|
273
|
+
print(this.economy.balance);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
```
|
|
277
|
+
|
|
278
|
+
Both files are under `src/server/services`, so the same `registerProviders` call registers both.
|
|
279
|
+
`Shop` gets the module's single `Economy` instance.
|
|
280
|
+
|
|
281
|
+
## Sharing code between realms
|
|
282
|
+
|
|
283
|
+
Put anything both realms need in a shared folder, and register that folder from both entry points.
|
|
284
|
+
Or wrap it in a plugin that both include; see [Plugins](08-plugins.md).
|
|
285
|
+
|
|
286
|
+
## Caveats
|
|
287
|
+
|
|
288
|
+
- **`disableDefaultLifecycle()` is silent.** With it, `onStart` never runs, and nothing warns you.
|
|
289
|
+
- **The path must be a string literal.** `registerProviders(SOME_CONSTANT)` fails to compile with
|
|
290
|
+
`Path is invalid, expected string literal`.
|
|
291
|
+
- **The path is a source path, not a Rojo path.** Write `"src/server/services"`, not
|
|
292
|
+
`"ServerScriptService/TS/services"`.
|
|
293
|
+
- **The folder has to hold a module, under its exact name.** A missing or misspelled folder still
|
|
294
|
+
compiles, with a build warning, and the call then waits for it at runtime. An empty folder
|
|
295
|
+
registers nothing, and waits in a fresh clone, which has no such folder. See [Rojo](#rojo).
|
|
296
|
+
- **The path is resolved in the project that compiles the call**, with that project's Rojo file. So
|
|
297
|
+
a published package cannot register its own folders: its `registerProviders("src/...")` gets a path
|
|
298
|
+
in the package's project, which a game's place does not have, and fails at runtime. In a package,
|
|
299
|
+
register classes one by one with `registerClassProvider`.
|
|
300
|
+
- **Providers are found where they are defined.** Registration requires each ModuleScript under the
|
|
301
|
+
folder. It takes every class the ModuleScript defines at its top level, exported or not, as v1
|
|
302
|
+
did. A class declared inside a function is found only if its file exports it.
|
|
303
|
+
- **`@Provider()` metadata must exist on both realms.** It is written when the decorator runs, and
|
|
304
|
+
none of it depends on the realm. So a class shared between realms behaves the same on both.
|
|
305
|
+
|
|
306
|
+
### Errors you may hit
|
|
307
|
+
|
|
308
|
+
| Message | Cause |
|
|
309
|
+
|---|---|
|
|
310
|
+
| `Could not find Rojo data for 'src/...'` | The folder is not mapped in your Rojo project file. |
|
|
311
|
+
| `Could not find Rojo data. There is no $path in your Rojo config that covers ...` (roblox-ts) | The file it names belongs to a package your code imports, and your Rojo project file does not map that package. See [Rojo](#rojo). |
|
|
312
|
+
| `Path is invalid, expected string literal and got: string` | The path argument is not a literal. |
|
|
313
|
+
| `class 'X' is missing the @Provider() decorator` | `registerClassProvider`/`registerProvider` was given an undecorated class. |
|
|
314
|
+
| `class 'X' is missing the @Provider() decorator: it inherits one from a parent class` | The class extends a provider but is not decorated itself. |
|
|
315
|
+
| `Flamework has no paths for the glob '...'` | A glob registration (`registerProvidersGlob`, `ComponentPlugin.fromGlob`, ...) in a package, the include directory not in the Rojo project, a string passed to `getGlobPaths`/`getClassesInGlob` that no glob macro made, or a `globs.json` from another build. A glob that matches no files does not raise this. It registers nothing, and the build prints a warning where it is used. |
|
|
316
|
+
| `ServerScriptService.TS.services.X failed to load (Nms): ...` | A ModuleScript under a registered path, or under a folder given to `requireModules`, raised an error while being required. |
|
|
317
|
+
| `requireModules("..."): the folder is not in the place` | The path is misspelled, or differs in case from the folder on disk; or the folder is empty and this is a clone without it (git keeps no empty folder); or it was moved or renamed after the build; or the Rojo project the place was built from leaves it out. The message names the part of the path that is missing, and the build warned about the first two where the path is written. See [Macros › Paths](07-macros.md#paths). |
|
|
318
|
+
| `...: registerProviders("..."): there is no such file or folder, so the place will not have it` (build warning) | A path given to a path macro names nothing in the place your Rojo project builds. With `on disk it is '...'`, only the case differs, and the place keeps the case the disk has. See [Rojo](#rojo). |
|
|
319
|
+
| `...: registerProviders("..."): nothing in that folder compiles to a module` (build warning) | The folder holds no module (only declarations, say). It arrives in the place empty and registers nothing; in a clone without the folder, the call waits for it. |
|
|
320
|
+
| `registerProviders("...") is still waiting for its folder: ... has no child named '...' after 5 seconds` (runtime warning) | The registration's folder is not in the place, for one of the reasons in the `requireModules` row above. It keeps waiting, in case the folder is still arriving. `ComponentPlugin.fromPath`, `registerComponents` and a plugin's `registerProviders` name themselves the same way. |
|
|
321
|
+
| `module '...' has been extinguished, cannot ...` | Something resolved from, or created an instance on, a module after `extinguish()`. |
|
|
322
|
+
| `provider ID was registered more than once: ...` | The same class was registered twice, often by two overlapping `registerProviders` paths. Raised at ignition. |
|
|
323
|
+
| `could not resolve dependency '...': it is registered but inactive` | The class is tied to a [scope](11-scopes.md) that this build does not have active. |
|
|
324
|
+
| `could not resolve dependency 'X': 'X' (...) is under registerProviders("..."), which is left out by its scope` | The class is under a folder whose registration has a [scope](11-scopes.md) condition this build does not meet, so the folder was never loaded. `getComponent` gives the same reason, starting `component '...' could not be found:`. |
|
|
325
|
+
| `could not resolve dependency '...': nothing registers it. Left out by their scope, without loading their folders: ...` | The module found nothing for the id, and some folder registrations were left out by their scope. If the class is under one of them, change the build's scopes or stop depending on it. |
|
|
326
|
+
| `could not resolve dependency 'X': 'X' (...) is a component (@Component), not a provider` | `Dependency<X>()`, `resolveDependency` or a constructor asked for a component. Components are built by `Components`: get one with `getComponent`, or make the class a `@Provider()`. The transformer refuses the plain `Dependency<X>()` and a provider's constructor parameter when you build. |
|
|
327
|
+
| `could not resolve dependency 'X': 'X' (...) is a @Provider() that nothing in this module registers or provides` | The class has loaded, but no path, registration, plugin or import of this module brings it in. |
|
|
328
|
+
| `module could not resolve dependency 'X'` | A constructor parameter's type is not registered in this module or any module it includes. |
|
|
329
|
+
| `'X' is a component (@Component), not a provider` (compile error) | `Dependency<X>()`, `resolveDependency<X>()` or a provider's constructor names a component. |
|
|
330
|
+
| `@Provider() on 'X': loadOrder must be a finite number` | `loadOrder` is `math.huge`, NaN or not a number. Raised when the ModuleScript that defines the class loads. |
|
|
331
|
+
|
|
332
|
+
---
|
|
333
|
+
|
|
334
|
+
Next: [Modules](02-modules.md)
|