toiljs 0.0.11 → 0.0.12
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 +2 -0
- package/build/cli/.tsbuildinfo +1 -1
- package/build/cli/configure.js +10 -4
- package/build/cli/create.js +58 -30
- package/build/cli/diagnostics.d.ts +55 -0
- package/build/cli/diagnostics.js +333 -0
- package/build/cli/doctor.d.ts +6 -0
- package/build/cli/doctor.js +249 -0
- package/build/cli/index.js +26 -0
- package/build/cli/proc.d.ts +5 -0
- package/build/cli/proc.js +20 -0
- package/build/cli/ui.d.ts +1 -0
- package/build/cli/ui.js +1 -0
- package/build/cli/update.d.ts +7 -0
- package/build/cli/update.js +117 -0
- package/build/cli/updates.d.ts +10 -0
- package/build/cli/updates.js +45 -0
- package/build/client/.tsbuildinfo +1 -1
- package/build/client/dev/error-overlay.js +1 -1
- package/build/client/head/metadata.js +3 -1
- package/build/client/index.d.ts +5 -1
- package/build/client/index.js +2 -0
- package/build/client/navigation/navigation.js +1 -1
- package/build/client/routing/Router.js +2 -2
- package/build/client/search/search.d.ts +26 -0
- package/build/client/search/search.js +101 -0
- package/build/client/search/use-page-search.d.ts +8 -0
- package/build/client/search/use-page-search.js +21 -0
- package/build/compiler/.tsbuildinfo +1 -1
- package/build/compiler/generate.js +26 -23
- package/build/compiler/index.d.ts +2 -0
- package/build/compiler/index.js +1 -0
- package/build/compiler/pages.d.ts +8 -0
- package/build/compiler/pages.js +37 -0
- package/build/compiler/plugin.js +3 -1
- package/build/compiler/prerender.d.ts +1 -0
- package/build/compiler/prerender.js +11 -5
- package/build/compiler/seo.js +10 -3
- package/build/io/.tsbuildinfo +1 -1
- package/examples/basic/client/components/Header.tsx +43 -41
- package/examples/basic/client/components/HoneycombBackground.tsx +223 -230
- package/examples/basic/client/public/index.html +18 -16
- package/examples/basic/client/routes/(legal)/privacy.tsx +18 -19
- package/examples/basic/client/routes/(legal)/terms.tsx +15 -16
- package/examples/basic/client/routes/about.tsx +21 -22
- package/examples/basic/client/routes/blog/[id].tsx +26 -18
- package/examples/basic/client/routes/features/actions.tsx +67 -67
- package/examples/basic/client/routes/features/error/index.tsx +27 -27
- package/examples/basic/client/routes/features/head.tsx +38 -38
- package/examples/basic/client/routes/features/index.tsx +83 -75
- package/examples/basic/client/routes/features/realtime.tsx +34 -32
- package/examples/basic/client/routes/features/script.tsx +31 -31
- package/examples/basic/client/routes/features/seo.tsx +39 -39
- package/examples/basic/client/routes/features/template/index.tsx +20 -20
- package/examples/basic/client/routes/features/template/template.tsx +16 -18
- package/examples/basic/client/routes/gallery/@modal/(.)photo/[id].tsx +23 -23
- package/examples/basic/client/routes/gallery/index.tsx +42 -42
- package/examples/basic/client/routes/gallery/photo/[id].tsx +18 -18
- package/examples/basic/client/routes/get-started.tsx +157 -84
- package/examples/basic/client/routes/index.tsx +137 -96
- package/examples/basic/client/routes/loader-demo/index.tsx +59 -52
- package/examples/basic/client/routes/search.tsx +61 -0
- package/examples/basic/client/routes/test.tsx +7 -8
- package/examples/basic/client/styles/main.css +624 -552
- package/package.json +2 -2
- package/presets/eslint.js +10 -3
- package/src/cli/configure.ts +363 -353
- package/src/cli/create.ts +563 -530
- package/src/cli/diagnostics.ts +421 -0
- package/src/cli/doctor.ts +318 -0
- package/src/cli/features.ts +166 -160
- package/src/cli/index.ts +242 -211
- package/src/cli/proc.ts +30 -0
- package/src/cli/ui.ts +111 -103
- package/src/cli/update.ts +150 -0
- package/src/cli/updates.ts +69 -0
- package/src/client/components/Image.tsx +91 -89
- package/src/client/dev/error-overlay.tsx +193 -197
- package/src/client/head/metadata.ts +94 -92
- package/src/client/index.ts +79 -64
- package/src/client/navigation/Link.tsx +94 -100
- package/src/client/navigation/navigation.ts +215 -218
- package/src/client/routing/Router.tsx +210 -193
- package/src/client/routing/hooks.ts +110 -114
- package/src/client/routing/lazy.ts +77 -81
- package/src/client/search/search.ts +189 -0
- package/src/client/search/use-page-search.ts +73 -0
- package/src/compiler/config.ts +173 -171
- package/src/compiler/fonts.ts +89 -87
- package/src/compiler/generate.ts +378 -373
- package/src/compiler/image-report.ts +88 -85
- package/src/compiler/index.ts +2 -0
- package/src/compiler/pages.ts +70 -0
- package/src/compiler/plugin.ts +51 -47
- package/src/compiler/prerender.ts +152 -130
- package/src/compiler/routes.ts +132 -131
- package/src/compiler/seo.ts +381 -356
- package/src/compiler/vite.ts +155 -145
- package/src/io/FastSet.ts +99 -96
- package/test/configure.test.ts +94 -90
- package/test/doctor.test.ts +140 -0
- package/test/dom/Image.test.tsx +73 -46
- package/test/dom/Script.test.tsx +48 -45
- package/test/dom/action.test.tsx +146 -129
- package/test/dom/error-overlay.test.tsx +44 -44
- package/test/dom/loader.test.tsx +2 -2
- package/test/dom/revalidate.test.tsx +1 -1
- package/test/dom/route-head.test.tsx +1 -2
- package/test/dom/slot.test.tsx +131 -109
- package/test/dom/view-transitions.test.tsx +53 -51
- package/test/features.test.ts +149 -142
- package/test/fonts.test.ts +28 -26
- package/test/head.test.ts +45 -35
- package/test/metadata.test.ts +42 -41
- package/test/pages.test.ts +105 -0
- package/test/prerender.test.ts +54 -46
- package/test/search.test.ts +114 -0
- package/test/seo.test.ts +164 -142
- package/test/update.test.ts +44 -0
package/src/compiler/vite.ts
CHANGED
|
@@ -1,145 +1,155 @@
|
|
|
1
|
-
import { createRequire } from 'node:module';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
import { pathToFileURL } from 'node:url';
|
|
4
|
-
|
|
5
|
-
import react from '@vitejs/plugin-react';
|
|
6
|
-
import { imagetools } from 'vite-imagetools';
|
|
7
|
-
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
8
|
-
import { mergeConfig, type InlineConfig, type PluginOption } from 'vite';
|
|
9
|
-
|
|
10
|
-
import { type ResolvedToilConfig } from './config.js';
|
|
11
|
-
import { fontPreloadPlugin } from './fonts.js';
|
|
12
|
-
import { imageReportPlugin } from './image-report.js';
|
|
13
|
-
import { toilPlugin } from './plugin.js';
|
|
14
|
-
import { prerenderPlugin } from './prerender.js';
|
|
15
|
-
|
|
16
|
-
// `vite-plugin-node-polyfills` rewrites react/react-dom to import its `vite-plugin-node-polyfills/
|
|
17
|
-
// shims/*` modules. When a consumer links toiljs by symlink (`file:`/workspace), that package lives
|
|
18
|
-
// only in toiljs's own node_modules, so resolving those bare specifiers from the consumer root
|
|
19
|
-
// fails ("Failed to resolve import vite-plugin-node-polyfills/shims/process"). Alias them to
|
|
20
|
-
// absolute paths resolved from toiljs's location so the build works however toiljs was installed.
|
|
21
|
-
const polyfillPkgRoot = path.dirname(
|
|
22
|
-
path.dirname(createRequire(import.meta.url).resolve('vite-plugin-node-polyfills')),
|
|
23
|
-
);
|
|
24
|
-
const polyfillShimAliases: Record<string, string> = {
|
|
25
|
-
'vite-plugin-node-polyfills/shims/buffer': path.join(
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
/**
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
//
|
|
104
|
-
|
|
105
|
-
//
|
|
106
|
-
cfg.
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
1
|
+
import { createRequire } from 'node:module';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { pathToFileURL } from 'node:url';
|
|
4
|
+
|
|
5
|
+
import react from '@vitejs/plugin-react';
|
|
6
|
+
import { imagetools } from 'vite-imagetools';
|
|
7
|
+
import { nodePolyfills } from 'vite-plugin-node-polyfills';
|
|
8
|
+
import { mergeConfig, type InlineConfig, type PluginOption } from 'vite';
|
|
9
|
+
|
|
10
|
+
import { type ResolvedToilConfig } from './config.js';
|
|
11
|
+
import { fontPreloadPlugin } from './fonts.js';
|
|
12
|
+
import { imageReportPlugin } from './image-report.js';
|
|
13
|
+
import { toilPlugin } from './plugin.js';
|
|
14
|
+
import { prerenderPlugin } from './prerender.js';
|
|
15
|
+
|
|
16
|
+
// `vite-plugin-node-polyfills` rewrites react/react-dom to import its `vite-plugin-node-polyfills/
|
|
17
|
+
// shims/*` modules. When a consumer links toiljs by symlink (`file:`/workspace), that package lives
|
|
18
|
+
// only in toiljs's own node_modules, so resolving those bare specifiers from the consumer root
|
|
19
|
+
// fails ("Failed to resolve import vite-plugin-node-polyfills/shims/process"). Alias them to
|
|
20
|
+
// absolute paths resolved from toiljs's location so the build works however toiljs was installed.
|
|
21
|
+
const polyfillPkgRoot = path.dirname(
|
|
22
|
+
path.dirname(createRequire(import.meta.url).resolve('vite-plugin-node-polyfills')),
|
|
23
|
+
);
|
|
24
|
+
const polyfillShimAliases: Record<string, string> = {
|
|
25
|
+
'vite-plugin-node-polyfills/shims/buffer': path.join(
|
|
26
|
+
polyfillPkgRoot,
|
|
27
|
+
'shims/buffer/dist/index.js',
|
|
28
|
+
),
|
|
29
|
+
'vite-plugin-node-polyfills/shims/global': path.join(
|
|
30
|
+
polyfillPkgRoot,
|
|
31
|
+
'shims/global/dist/index.js',
|
|
32
|
+
),
|
|
33
|
+
'vite-plugin-node-polyfills/shims/process': path.join(
|
|
34
|
+
polyfillPkgRoot,
|
|
35
|
+
'shims/process/dist/index.js',
|
|
36
|
+
),
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
/** Image extensions routed to `images/` in the build output. */
|
|
40
|
+
const IMAGE_EXT = /^(png|jpe?g|svg|gif|tiff|bmp|ico|webp|avif)$/i;
|
|
41
|
+
/** Font extensions routed to `fonts/`. */
|
|
42
|
+
const FONT_EXT = /^(woff|woff2|eot|ttf|otf)$/i;
|
|
43
|
+
|
|
44
|
+
/** Routes a built asset to a typed sub-folder (`images/`, `fonts/`, `css/`, else `assets/`). */
|
|
45
|
+
function assetFileName(name: string): string {
|
|
46
|
+
const ext = name.split('.').pop() ?? '';
|
|
47
|
+
if (IMAGE_EXT.test(ext)) return 'images/[name][extname]';
|
|
48
|
+
if (FONT_EXT.test(ext)) return 'fonts/[name][extname]';
|
|
49
|
+
if (/^css$/i.test(ext)) return 'css/[name][extname]';
|
|
50
|
+
return 'assets/[name][extname]';
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Loads the Tailwind v4 Vite plugin if the project has `@tailwindcss/vite` installed (added by
|
|
55
|
+
* `toiljs create`/`configure` when Tailwind is enabled). Resolved from the project root so it picks
|
|
56
|
+
* up the project's copy; returns `undefined` when Tailwind is off, so the plugin simply isn't added.
|
|
57
|
+
*/
|
|
58
|
+
async function tailwindPlugin(root: string): Promise<PluginOption | undefined> {
|
|
59
|
+
let resolved: string;
|
|
60
|
+
try {
|
|
61
|
+
resolved = createRequire(path.join(root, 'package.json')).resolve('@tailwindcss/vite');
|
|
62
|
+
} catch {
|
|
63
|
+
return undefined;
|
|
64
|
+
}
|
|
65
|
+
const mod = (await import(pathToFileURL(resolved).href)) as { default?: () => PluginOption };
|
|
66
|
+
return mod.default?.();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Splits React's runtime into its own long-lived chunk for better caching. */
|
|
70
|
+
function manualChunks(id: string): string | undefined {
|
|
71
|
+
if (!id.includes('node_modules')) return undefined;
|
|
72
|
+
if (
|
|
73
|
+
id.includes('node_modules/react-dom') ||
|
|
74
|
+
id.includes('node_modules/react/') ||
|
|
75
|
+
id.includes('node_modules/scheduler')
|
|
76
|
+
) {
|
|
77
|
+
return 'react';
|
|
78
|
+
}
|
|
79
|
+
return undefined;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Builds the framework-owned Vite config. Vite's `root` is the generated `.toil` dir so its
|
|
84
|
+
* `index.html` (built from the project's `public/index.html` template) emits at the output root
|
|
85
|
+
* with assets resolving correctly; static `public/` assets are mirrored to `.toil/public` and
|
|
86
|
+
* picked up via Vite's default publicDir. `fs.allow` opens the project (for `client/`) and the
|
|
87
|
+
* framework runtime. The opinionated default, Node polyfills
|
|
88
|
+
* (Buffer/global/process), React plugin, toil route plugin, typed asset folders, React chunk
|
|
89
|
+
* splitting and tuned build options, is applied here; `toiljs/client` is aliased to the
|
|
90
|
+
* runtime, and the user's `client.vite` overrides deep-merge on top.
|
|
91
|
+
*/
|
|
92
|
+
export async function createViteConfig(cfg: ResolvedToilConfig): Promise<InlineConfig> {
|
|
93
|
+
const frameworkRoot = path.resolve(path.dirname(cfg.runtimePath), '..', '..');
|
|
94
|
+
const tailwind = await tailwindPlugin(cfg.root);
|
|
95
|
+
|
|
96
|
+
const base: InlineConfig = {
|
|
97
|
+
root: cfg.toilDir,
|
|
98
|
+
base: cfg.base,
|
|
99
|
+
configFile: false,
|
|
100
|
+
plugins: [
|
|
101
|
+
tailwind,
|
|
102
|
+
// Build-time image resize/optimization. Every *imported* raster image is compressed to
|
|
103
|
+
// webp by default (so a plain `<img src={imported}>` is optimized too, not just
|
|
104
|
+
// `Toil.Image`); add `?w=400;800&format=…` to resize or pick a format. `public/` assets
|
|
105
|
+
// referenced by string path are served as-is. Disabled by `client.images: false`.
|
|
106
|
+
cfg.images
|
|
107
|
+
? imagetools({
|
|
108
|
+
defaultDirectives: () =>
|
|
109
|
+
new URLSearchParams({ format: 'webp', quality: '80' }),
|
|
110
|
+
})
|
|
111
|
+
: undefined,
|
|
112
|
+
cfg.images ? imageReportPlugin(cfg.root, cfg.toilDir) : undefined,
|
|
113
|
+
// Static per-route SEO prerender (build only): bakes each route's metadata into its HTML.
|
|
114
|
+
cfg.seo ? prerenderPlugin(cfg) : undefined,
|
|
115
|
+
// Preload bundled fonts (build only). Disabled by `client.fonts: false`.
|
|
116
|
+
cfg.fonts ? fontPreloadPlugin(cfg) : undefined,
|
|
117
|
+
nodePolyfills({ globals: { Buffer: true, global: true, process: true } }),
|
|
118
|
+
react(),
|
|
119
|
+
toilPlugin(cfg),
|
|
120
|
+
],
|
|
121
|
+
resolve: {
|
|
122
|
+
alias: {
|
|
123
|
+
'toiljs/client': cfg.runtimePath,
|
|
124
|
+
'toiljs/routes': path.join(cfg.toilDir, 'routes.ts'),
|
|
125
|
+
...polyfillShimAliases,
|
|
126
|
+
},
|
|
127
|
+
dedupe: ['react', 'react-dom'],
|
|
128
|
+
},
|
|
129
|
+
server: {
|
|
130
|
+
port: cfg.port,
|
|
131
|
+
fs: { allow: [cfg.root, frameworkRoot] },
|
|
132
|
+
},
|
|
133
|
+
build: {
|
|
134
|
+
outDir: path.resolve(cfg.root, cfg.outDir),
|
|
135
|
+
emptyOutDir: true,
|
|
136
|
+
target: 'es2020',
|
|
137
|
+
modulePreload: false,
|
|
138
|
+
cssCodeSplit: false,
|
|
139
|
+
assetsInlineLimit: 10000,
|
|
140
|
+
chunkSizeWarningLimit: 3000,
|
|
141
|
+
commonjsOptions: {
|
|
142
|
+
strictRequires: true,
|
|
143
|
+
transformMixedEsModules: true,
|
|
144
|
+
},
|
|
145
|
+
rollupOptions: {
|
|
146
|
+
output: {
|
|
147
|
+
chunkFileNames: 'assets/[name]-[hash].js',
|
|
148
|
+
assetFileNames: (assetInfo) => assetFileName(assetInfo.names[0] ?? ''),
|
|
149
|
+
manualChunks,
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
},
|
|
153
|
+
};
|
|
154
|
+
return mergeConfig(base, cfg.vite);
|
|
155
|
+
}
|
package/src/io/FastSet.ts
CHANGED
|
@@ -1,96 +1,99 @@
|
|
|
1
|
-
import { type FastRecord, type IndexKey, type PropertyExtendedKey } from './FastMap.js';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* The Set counterpart to {@link FastMap}: an insertion-ordered set backed by an array (for
|
|
5
|
-
* iteration/ordering) plus a record index (for O(1) membership), with bigint-key support.
|
|
6
|
-
*
|
|
7
|
-
* Authored to match FastMap's design, the upstream package ships no `FastSet`.
|
|
8
|
-
*/
|
|
9
|
-
export class FastSet<T extends PropertyExtendedKey> implements Disposable {
|
|
10
|
-
protected _values: T[] = [];
|
|
11
|
-
protected _index: FastRecord<true> = {};
|
|
12
|
-
|
|
13
|
-
constructor(iterable?: Iterable<T> | null | FastSet<T>) {
|
|
14
|
-
if (iterable instanceof FastSet) {
|
|
15
|
-
this.addAll(iterable);
|
|
16
|
-
} else if (iterable) {
|
|
17
|
-
for (const value of iterable) {
|
|
18
|
-
this.add(value);
|
|
19
|
-
}
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
public get size(): number {
|
|
24
|
-
return this._values.length;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
public add(value: T): this {
|
|
28
|
-
if (!this.has(value)) {
|
|
29
|
-
this._values.push(value);
|
|
30
|
-
this._index[value as IndexKey] = true;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
return this;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
public has(value: T): boolean {
|
|
37
|
-
return Object.prototype.hasOwnProperty.call(this._index, value as IndexKey);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
public indexOf(value: T): number {
|
|
41
|
-
for (let i = 0; i < this._values.length; i++) {
|
|
42
|
-
if (this._values[i] === value) {
|
|
43
|
-
return i;
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
return -1;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
public delete(value: T): boolean {
|
|
51
|
-
if (!this.has(value)) {
|
|
52
|
-
return false;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const index = this.indexOf(value);
|
|
56
|
-
if (index !== -1) {
|
|
57
|
-
this._values.splice(index, 1);
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
delete this._index[value as IndexKey];
|
|
61
|
-
return true;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
public addAll(set: FastSet<T>): void {
|
|
65
|
-
for (const value of set.values()) {
|
|
66
|
-
this.add(value);
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
public *values(): IterableIterator<T> {
|
|
71
|
-
yield* this._values;
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
public *keys(): IterableIterator<T> {
|
|
75
|
-
yield* this._values;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
public forEach(
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
1
|
+
import { type FastRecord, type IndexKey, type PropertyExtendedKey } from './FastMap.js';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The Set counterpart to {@link FastMap}: an insertion-ordered set backed by an array (for
|
|
5
|
+
* iteration/ordering) plus a record index (for O(1) membership), with bigint-key support.
|
|
6
|
+
*
|
|
7
|
+
* Authored to match FastMap's design, the upstream package ships no `FastSet`.
|
|
8
|
+
*/
|
|
9
|
+
export class FastSet<T extends PropertyExtendedKey> implements Disposable {
|
|
10
|
+
protected _values: T[] = [];
|
|
11
|
+
protected _index: FastRecord<true> = {};
|
|
12
|
+
|
|
13
|
+
constructor(iterable?: Iterable<T> | null | FastSet<T>) {
|
|
14
|
+
if (iterable instanceof FastSet) {
|
|
15
|
+
this.addAll(iterable);
|
|
16
|
+
} else if (iterable) {
|
|
17
|
+
for (const value of iterable) {
|
|
18
|
+
this.add(value);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
public get size(): number {
|
|
24
|
+
return this._values.length;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
public add(value: T): this {
|
|
28
|
+
if (!this.has(value)) {
|
|
29
|
+
this._values.push(value);
|
|
30
|
+
this._index[value as IndexKey] = true;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
public has(value: T): boolean {
|
|
37
|
+
return Object.prototype.hasOwnProperty.call(this._index, value as IndexKey);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
public indexOf(value: T): number {
|
|
41
|
+
for (let i = 0; i < this._values.length; i++) {
|
|
42
|
+
if (this._values[i] === value) {
|
|
43
|
+
return i;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return -1;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
public delete(value: T): boolean {
|
|
51
|
+
if (!this.has(value)) {
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const index = this.indexOf(value);
|
|
56
|
+
if (index !== -1) {
|
|
57
|
+
this._values.splice(index, 1);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
delete this._index[value as IndexKey];
|
|
61
|
+
return true;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
public addAll(set: FastSet<T>): void {
|
|
65
|
+
for (const value of set.values()) {
|
|
66
|
+
this.add(value);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
public *values(): IterableIterator<T> {
|
|
71
|
+
yield* this._values;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
public *keys(): IterableIterator<T> {
|
|
75
|
+
yield* this._values;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
public forEach(
|
|
79
|
+
callback: (value: T, value2: T, set: FastSet<T>) => void,
|
|
80
|
+
thisArg?: unknown,
|
|
81
|
+
): void {
|
|
82
|
+
for (const value of this._values) {
|
|
83
|
+
callback.call(thisArg, value, value, this);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
public clear(): void {
|
|
88
|
+
this._values = [];
|
|
89
|
+
this._index = {};
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
public [Symbol.dispose](): void {
|
|
93
|
+
this.clear();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
*[Symbol.iterator](): IterableIterator<T> {
|
|
97
|
+
yield* this._values;
|
|
98
|
+
}
|
|
99
|
+
}
|