Skip to content

Commit

Permalink
feat: release alpine-provide-inject plugin
Browse files Browse the repository at this point in the history
  • Loading branch information
JuroOravec committed Jul 28, 2024
1 parent 4980b5c commit 13d413b
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 0 deletions.
24 changes: 24 additions & 0 deletions packages/alpine-provide-inject/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
59 changes: 59 additions & 0 deletions packages/alpine-provide-inject/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# Alpine Provide Inject

Adds `$provide` and `$inject` magics. These work similar to Vue's `provide`/`inject`:

```html
<div
x-data="{
outerValue: 'Outer Value'
}"
x-init="$provide('key', outerValue)"
>
<div
x-data="{
innerValue: 'Inner Value'
}"
x-init="$provide('key', innerValue)"
>
<div x-data>
<!-- This will show "Inner Value" -->
<span x-text="$inject('key')"></span>
</div>
</div>
<div x-data>
<!-- This will show "Outer Value" -->
<span x-text="$inject('key')"></span>

<!--
Similar to `$inject`, but will return value only
if the closest component provided the key
-->
<span x-text="$injectSelf('key')"></span>
</div>
</div>
```

## Installation

### Via CDN

```html
<script defer src="https://cdn.jsdelivr.net/npm/[email protected]/dist/cdn.min.js"></script>
```

### Via NPM

```sh
npm install alpine-provide-inject
```

Then initialize it from your bundle:

```js
import Alpine from 'alpinejs'
import ProvideInject from 'alpine-provide-inject'

Alpine.plugin(ProvideInject)

...
```
8 changes: 8 additions & 0 deletions packages/alpine-provide-inject/builds/cdn.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Adapted from the oficial Alpine.js file:
// https://github.com/alpinejs/alpine/blob/main/packages/focus/builds/cdn.js

import ProvideInjectPlugin from '../src/index.js'

document.addEventListener('alpine:init', () => {
window.Alpine.plugin(ProvideInjectPlugin)
})
6 changes: 6 additions & 0 deletions packages/alpine-provide-inject/builds/module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// Adapted from the oficial Alpine.js file:
// https://github.com/alpinejs/alpine/blob/main/packages/focus/builds/module.js

import ProvideInjectPlugin from '../src/index.ts'

export default ProvideInjectPlugin
23 changes: 23 additions & 0 deletions packages/alpine-provide-inject/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"name": "alpine-provide-inject",
"private": false,
"version": "0.2.0",
"type": "commonjs",
"main": "dist/module.cjs.js",
"module": "dist/module.esm.js",
"homepage": "https://github.com/JuroOravec/alpinui/blob/main/packages/alpine-provide-inject",
"repository": {
"type": "git",
"url": "git+https://github.com/JuroOravec/alpinui.git",
"directory": "packages/alpine-provide-inject"
},
"author": "Juro Oravec",
"license": "MIT",
"scripts": {
"build": "node scripts/build.js"
},
"devDependencies": {
"esbuild": "^0.23.0",
"typescript": "^5.2.2"
}
}
79 changes: 79 additions & 0 deletions packages/alpine-provide-inject/scripts/build.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// Adapted from the oficial Alpine.js file:
// https://github.com/alpinejs/alpine/blob/main/scripts/build.js

const fs = require('fs')
const zlib = require('zlib')

bundle()

function bundle() {
// Give esbuild specific configurations to build.

// This output file is meant to be loaded in a browser's <script> tag.
build({
entryPoints: ['builds/cdn.ts'],
outfile: 'dist/cdn.js',
bundle: true,
platform: 'browser',
define: { CDN: 'true' },
})

// Build a minified version.
build({
entryPoints: ['builds/cdn.ts'],
outfile: 'dist/cdn.min.js',
bundle: true,
minify: true,
platform: 'browser',
define: { CDN: 'true' },
}).then(() => {
outputSize('dist/cdn.min.js')
})

// Then output two files: an esm module and a cjs module.
// The ESM one is meant for "import" statements (bundlers and new browsers)
// and the cjs one is meant for "require" statements (node).
build({
entryPoints: ['builds/module.ts'],
outfile: 'dist/module.esm.js',
bundle: true,
platform: 'neutral',
mainFields: ['module', 'main'],
})

build({
entryPoints: ['builds/module.ts'],
outfile: 'dist/module.cjs.js',
bundle: true,
target: ['node10.4'],
platform: 'node',
})
}

function build(options) {
options.define || (options.define = {})
options.define['process.env.NODE_ENV'] = process.argv.includes('--watch')
? `'production'`
: `'development'`

return require('esbuild')
.build({
// external: ['alpinejs'],
...options,
})
.catch(() => process.exit(1))
}

function outputSize(file) {
const size = bytesToSize(zlib.brotliCompressSync(fs.readFileSync(file)).length)

console.log('\x1b[32m', `${file}: ${size}`)
}

function bytesToSize(bytes) {
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB']
if (bytes === 0) return 'n/a'
const i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)), 10)
if (i === 0) return `${bytes} ${sizes[i]}`
return `${(bytes / 1024 ** i).toFixed(1)} ${sizes[i]}`
}
5 changes: 5 additions & 0 deletions packages/alpine-provide-inject/src/global.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import type { Alpine } from 'alpinejs'

declare global {
const Alpine: Alpine
}
67 changes: 67 additions & 0 deletions packages/alpine-provide-inject/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
// Types
import type { Alpine as AlpineType } from 'alpinejs'
import type { InjectionKey } from 'vue'

declare global {
interface Element {
_provides?: Record<string | symbol, any>;
}
}

declare module 'alpinejs' {
export interface Magics<T> {
$provide: <T, K = InjectionKey<T> | string | number>(
key: K,
value: K extends InjectionKey<infer V> ? V : T
) => void;
$inject: <T = any>(key: InjectionKey<T> | string, defaultVal?: T) => T;
$injectSelf: <T = any>(key: InjectionKey<T> | string, defaultVal?: T) => T;
}
}

export default function AlpineProvideInjectPlugin(Alpine: AlpineType) {
Alpine.magic('provide', (el) => {
return (key: symbol | string | number, value: any) => {
if (!el._provides) el._provides = {}
el._provides[key] = value
}
})

const injectFn = (
el: HTMLElement | null,
key: string | symbol,
defaultVal: any,
searchAncestors: boolean,
) => {
let currentEl: HTMLElement | null = el

while (currentEl) {
if (currentEl._provides && key in currentEl._provides) {
return currentEl._provides[key]
}

if (searchAncestors) {
currentEl = currentEl.parentElement
} else {
break
}
}

if (defaultVal !== undefined) {
return defaultVal
} else {
throw Error(`[alpine-provide-inject]: Tried to inject undefined key '${key.toString()}'`)
}
}

// NOTE: $inject strictly looks for values in ancestors
Alpine.magic('inject', (el) => {
return (key: string | symbol, defaultVal?: any) => injectFn(el.parentElement, key, defaultVal, true)
})

// This is same as Vuetify's injectSelf - The value injected
// is searched ONLY in the current element.
Alpine.magic('injectSelf', (el) => {
return (key: string | symbol, defaultVal?: any) => injectFn(el, key, defaultVal, false)
})
}
24 changes: 24 additions & 0 deletions packages/alpine-provide-inject/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"module": "ESNext",
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"skipLibCheck": true,

/* Bundler mode */
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"moduleDetection": "force",
"noEmit": true,

/* Linting */
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true
},
"include": ["src", "dev/src/vite-env.d.ts"]
}

0 comments on commit 13d413b

Please sign in to comment.