Skip to content

Commit a989dba

Browse files
authored
Merge pull request #15 from bholmesdev/refactor/migrate-simple-scope
Migrate vite-plugin-simple-scope to the stack
2 parents 22012df + 42c24c3 commit a989dba

File tree

8 files changed

+450
-15
lines changed

8 files changed

+450
-15
lines changed

README.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,8 @@ Simple stack is a suite of tools built for [Astro](https://astro.build) to simpl
88

99
Simple stack offers a growing collection of packages:
1010

11-
- **[Simple form](https://github.com/bholmesdev/simple-stack/tree/main/packages/form):** A full stack solution to validate forms with your client framework of choice.
11+
- **[🧘‍♂️ Simple form](https://github.com/bholmesdev/simple-stack/tree/main/packages/form):** A full stack solution to validate forms with your client framework of choice.
12+
- **[🔎 Simple scope](https://github.com/bholmesdev/simple-stack/tree/main/packages/scope):** Get a scoped ID for any file you're in. Perfect for form label IDs and query selectors.
1213
- **[⏳ WIP: Simple partial](https://github.com/bholmesdev/simple-stack/tree/main/packages/partial):** Re-render just the parts that change in your Astro app.
1314

1415
## Get involved

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
"format:write": "biome format --write"
1616
},
1717
"keywords": ["withastro"],
18-
"author": "@bholmesdev",
18+
"author": "bholmesdev",
1919
"license": "MIT",
2020
"devDependencies": {
2121
"@biomejs/biome": "^1.4.1",

packages/scope/README.md

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# simple scope 🔎
2+
3+
Get a scoped ID for whatever file you're in. Resolved at build-time with zero client JS.
4+
5+
```jsx
6+
import { scope } from 'simple:scope';
7+
8+
function Form() {
9+
return (
10+
<form>
11+
<label htmlFor={scope('email')}>Email</label>
12+
<input id={scope('email')} name="email" />
13+
</form>
14+
);
15+
}
16+
17+
/*
18+
Output:
19+
20+
<form>
21+
<label for="email-dj23i_ka">Email</label>
22+
<input id="email-dj23i_ka" name="email">
23+
</form>
24+
*/
25+
```
26+
27+
## Installation
28+
29+
Simple scope is a vite plugin compatible with any vite-based framework (Astro, Nuxt, SvelteKit, etc). First install the dependency from npm:
30+
31+
```bash
32+
npm i vite-plugin-simple-scope
33+
```
34+
35+
Then, set up type inferencing for the `simple:scope` module with an `env.d.ts` file. You can create this file at the base of your project, or add to the provided `src/env.d.ts` file for frameworks like Astro:
36+
37+
```ts
38+
// env.d.ts
39+
/// <reference types="vite-plugin-simple-scope/types" />
40+
```
41+
42+
Finally, apply as a vite plugin in your framework of choice:
43+
44+
```js
45+
import simpleScope from 'vite-plugin-simple-scope';
46+
47+
// apply `simpleScope()` to your vite plugin config
48+
```
49+
50+
- [Astro vite plugin configuration](https://docs.astro.build/en/recipes/add-yaml-support/)
51+
- [Nuxt vite plugin configuration](https://nuxt.com/docs/getting-started/configuration#external-configuration-files)
52+
- [SvelteKit vite plugin configuration](https://kit.svelte.dev/docs/project-structure#project-files-vite-config-js)
53+
54+
## Usage
55+
56+
You can import the `scope()` utility from `simple:scope` in any JavaScript-based file. This function accepts an optional prefix string for naming different scoped identifiers.
57+
58+
Since `scope()` uses the file path to generate IDs, multiple calls to `scope()` will append the same value:
59+
60+
```js
61+
// example.js
62+
63+
scope(); // JYZeLezU
64+
scope('first'); // first-JYZeLezU
65+
scope('second'); // second-JYZeLezU
66+
```
67+
68+
Simple scope will also generate the same ID when called server-side or client-side. This prevents hydration mismatches when using component frameworks like React or Vue, and is helpful when querying scoped element `id`s from the DOM.
69+
70+
This example uses [Astro](https://astro.build) to add a scoped `id` to a `<canvas>` element, and queries that `id` from a client-side `<script>`:
71+
72+
```astro
73+
---
74+
// Server-side template
75+
import { scope } from 'simple:scope';
76+
---
77+
78+
<canvas id={scope('canvas')}></canvas>
79+
80+
<script>
81+
// Client-side script
82+
import { scope } from 'simple:scope';
83+
84+
const canvas = document.getElementById(scope('canvas'));
85+
</script>
86+
```

packages/scope/index.js

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { nanoid } from "nanoid";
2+
3+
const virtualMod = "simple:scope";
4+
5+
/** @returns {import('vite').Plugin} */
6+
export default function simpleScope() {
7+
/** @type {Record<string, string>} */
8+
const scopeIdByImporter = {};
9+
10+
return {
11+
name: "vite-plugin-simple-scope",
12+
resolveId(id, rawImporter) {
13+
if (id !== virtualMod || !rawImporter) return;
14+
15+
const importer = getBaseFilePath(rawImporter);
16+
if (!scopeIdByImporter[importer]) {
17+
scopeIdByImporter[importer] = nanoid(8);
18+
}
19+
return `${virtualMod}/${scopeIdByImporter[importer]}`;
20+
},
21+
async load(id) {
22+
const [maybeVirtualMod, scopeId] = id.split("/");
23+
if (maybeVirtualMod !== virtualMod || !scopeId) return;
24+
25+
return `const scopeId = ${JSON.stringify(scopeId)};
26+
export function scope(id) {
27+
if (!id) return scopeId;
28+
29+
return id + '-' + scopeId;
30+
}`;
31+
},
32+
};
33+
}
34+
35+
/**
36+
* Vite supports file search params with `?`.
37+
* Trim these off to get the base file path.
38+
*
39+
* @param {string} filePath
40+
* @returns {string}
41+
*/
42+
function getBaseFilePath(filePath) {
43+
return filePath.replace(/\?.*$/, "");
44+
}

packages/scope/package.json

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
{
2+
"name": "vite-plugin-simple-scope",
3+
"version": "1.0.3",
4+
"description": "Get a scoped ID for whatever file you're in. Zero client-side JS.",
5+
"type": "module",
6+
"exports": {
7+
".": "./index.js",
8+
"./types": "./types.d.ts"
9+
},
10+
"files": ["types.d.ts", "index.js"],
11+
12+
"repository": {
13+
"type": "git",
14+
"url": "https://github.com/bholmesdev/simple-stack.git",
15+
"directory": "packages/scope"
16+
},
17+
"scripts": {},
18+
"dependencies": {
19+
"nanoid": "^5.0.3"
20+
},
21+
"devDependencies": {
22+
"vite": "^4.5.0"
23+
},
24+
"author": "bholmesdev",
25+
"license": "MIT",
26+
"engines": {
27+
"node": ">=18.14.1"
28+
}
29+
}

packages/scope/tsconfig.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"compilerOptions": {
3+
"checkJs": true,
4+
"module": "ESNext",
5+
"moduleResolution": "Node",
6+
"target": "ESNext",
7+
"noEmit": true,
8+
"strict": true
9+
}
10+
}

packages/scope/types.d.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
declare module "simple:scope" {
2+
export function scope(prefix?: string): string;
3+
}

0 commit comments

Comments
 (0)