@shopify/shop-minis-react
Version:
React component library for Shopify Shop Minis with Tailwind CSS v4 support (source-only, requires TypeScript)
311 lines (228 loc) • 8.11 kB
Markdown
# Shop Minis ESLint Plugin
Custom ESLint rules for Shop Minis apps. ESLint is included with the SDK.
## Quick Setup
Create **`eslint.config.js`** (NOT `.eslintrc.js`):
```javascript
const shopMinisConfig = require('@shopify/shop-minis-react/eslint/config')
module.exports = [shopMinisConfig]
```
**That's it!** TypeScript and JSX are supported out of the box.
**Important:**
- File must be named `eslint.config.js` (no dot, no "rc")
- This will lint all `.js`, `.jsx`, `.ts`, `.tsx` files in your project
- TypeScript and JSX parsing is configured automatically
## Usage
```bash
# Check for errors
npx eslint .
# Auto-fix (converts <img> to <Image> AND adds import!)
npx eslint . --fix
```
## What You Get
### Custom Shop Minis Rules
- ✅ No internal imports allowed
- ✅ Warnings for `<img>`, `<button>`, `<label>` tags (auto-fixes with imports)
- ✅ Manifest scope validation - ensures manifest.json has scopes for hooks you use (auto-fixes manifest)
### Security Rules (Using Built-in ESLint Rules)
- ✅ WebAssembly usage blocked - prevents WASM in Shop Minis environment
- ✅ Unsafe code execution blocked - prevents `eval()`, Function constructor, and dynamic code execution
- ✅ `dangerouslySetInnerHTML` blocked - prevents XSS vulnerabilities
- ✅ `window.open` blocked - use SDK navigation instead
- ✅ Navigator APIs blocked - `clipboard`, `credentials`, `geolocation`, `share` are not available
## Rules
### `no-internal-imports`
Prevents importing from internal SDK directories.
```tsx
// ❌ Error
import {something} from '@shopify/shop-minis-react/internal'
// ✅ Correct
import {Component} from '@shopify/shop-minis-react'
```
### `prefer-sdk-components`
Suggests using SDK components instead of native HTML elements. **Fully auto-fixable** - fixes both tags and imports!
**Before:**
```tsx
const MyComponent = () => (
<img src="product.jpg" alt="Product" />
)
```
**After running `npx eslint . --fix`:**
```tsx
import {Image} from '@shopify/shop-minis-react'
const MyComponent = () => (
<Image src="product.jpg" alt="Product" />
)
```
**Supported Components:**
- `<img>` → `<Image>`
- `<button>` → `<Button>`
- `<label>` → `<Label>`
**Auto-fix does TWO things:**
1. ✅ Replaces native element with SDK component
2. ✅ Adds import statement automatically (or adds to existing import)
### `validate-manifest`
Validates `src/manifest.json` configuration for scopes and permissions. **Auto-fixable** - adds missing values to manifest!
#### Scopes
Checks that hooks have required scopes:
```tsx
// If you use this hook:
import {useCurrentUser} from '@shopify/shop-minis-react'
// Manifest must include:
{
"scopes": ["USER_SETTINGS_READ"]
}
```
**Scope Requirements:**
- `useCurrentUser` → `USER_SETTINGS_READ`
- `useSavedProducts` → `FAVORITES`
- `useOrders` → `ORDERS`
#### Permissions
Checks for native permission usage:
```tsx
// If you use this hook:
import {useImagePicker} from '@shopify/shop-minis-react'
// Or browser APIs:
navigator.mediaDevices.getUserMedia({video: true})
// Manifest must include:
{
"permissions": ["CAMERA"]
}
```
**Supported Permissions:**
- `CAMERA` - Required for `useImagePicker` hook or getUserMedia video
- `MICROPHONE` - Required for getUserMedia audio
- `MOTION` - Required for DeviceOrientation/DeviceMotion events
#### Trusted Domains
Checks that external URLs are in trusted_domains. Detects:
**Network Requests:**
- `fetch('https://api.example.com/data')`
- `new XMLHttpRequest().open('GET', 'https://...')`
- `new WebSocket('wss://api.example.com')`
- `new EventSource('https://api.example.com/events')`
- `navigator.sendBeacon('https://analytics.example.com')`
- `window.open('https://external.com')`
**Media & Resources:**
- `<img src="https://cdn.shopify.com/image.jpg" />`
- `<video src="https://videos.example.com/video.mp4" />`
- `<video poster="https://cdn.example.com/poster.jpg" />`
- `<audio src="https://audio.example.com/sound.mp3" />`
- `<source src="https://media.example.com/video.mp4" />`
- `<track src="https://cdn.example.com/captions.vtt" />`
- `<object data="https://cdn.example.com/file.pdf" />`
- `<embed src="https://cdn.example.com/file.swf" />`
- `<form action="https://api.example.com/submit" />`
**Note:** External scripts (`<script>`), stylesheets (`<link>`), and iframes are not supported and excluded from validation.
**Example manifest:**
```json
{
"trusted_domains": [
"api.example.com",
"cdn.shopify.com",
"videos.example.com"
]
}
```
**Wildcard support:**
```json
{
"trusted_domains": [
"*.shopify.com", // Matches any Shopify subdomain
"api.example.com", // Exact domain
"cdn.example.com/assets" // Specific path
]
}
```
**Auto-fix:**
```bash
npx eslint . --fix
# Automatically updates src/manifest.json
```
**Example errors:**
```
Hook "useCurrentUser" requires scope "USER_SETTINGS_READ" in src/manifest.json.
Hook "useImagePicker" requires permission "CAMERA" in src/manifest.json.
fetch() call loads from "api.example.com" which is not in trusted_domains.
<img> src attribute loads from "cdn.shopify.com" which is not in trusted_domains.
```
## Security Rules
The following security restrictions are enforced using standard ESLint plugins and built-in rules:
### WebAssembly Restrictions
**Rules:** `no-restricted-globals`, `no-restricted-syntax`
```tsx
// ❌ Error
const module = new WebAssembly.Module(bytes)
WebAssembly.instantiate(bytes)
WebAssembly.compile(bytes)
// ✅ Correct
// Use JavaScript implementations instead of WebAssembly
```
**Why:** WebAssembly is not supported in the Shop Minis security sandbox.
### Unsafe Code Execution
**Rules:** `no-eval`, `no-implied-eval`, `no-new-func`, `no-script-url` (built-in ESLint rules)
```tsx
// ❌ Error
eval('alert(1)')
new Function('a', 'b', 'return a + b')
setTimeout('alert(1)', 1000)
window.location = 'javascript:void(0)'
// ✅ Correct
const add = (a, b) => a + b
setTimeout(() => alert(1), 1000)
window.location = 'https://example.com'
```
**Why:** Dynamic code execution can bypass security restrictions.
**Built-in rules used:**
- `no-eval` - blocks `eval()` and `window.eval()`
- `no-new-func` - blocks `new Function()` constructor
- `no-implied-eval` - blocks `setTimeout()` / `setInterval()` with string arguments
- `no-script-url` - blocks `javascript:` URLs
### Dangerous HTML Injection
**Rule:** `react/no-danger`
```tsx
// ❌ Error
<div dangerouslySetInnerHTML={{__html: userInput}} />
// ✅ Correct
<div>{sanitizedContent}</div>
```
**Why:** Injecting raw HTML can lead to XSS (Cross-Site Scripting) attacks.
### Window Open Restriction
**Rule:** `no-restricted-syntax`
```tsx
// ❌ Error
window.open('https://example.com', '_blank')
// ✅ Correct
// Use SDK navigation methods instead
import {useNavigation} from '@shopify/shop-minis-react'
const {navigate} = useNavigation()
navigate('https://example.com')
```
**Why:** `window.open` is not allowed in the Shop Minis environment. Use SDK navigation methods instead.
### Navigator API Restrictions
**Rule:** `no-restricted-syntax`
The following Navigator APIs are not available in the Shop Minis environment:
```tsx
// ❌ Error - Clipboard API
navigator.clipboard.writeText('text')
navigator.clipboard.readText()
// ❌ Error - Credentials API
navigator.credentials.get({password: true})
navigator.credentials.store(credential)
// ❌ Error - Geolocation API
navigator.geolocation.getCurrentPosition(callback)
navigator.geolocation.watchPosition(callback)
// ❌ Error - Share API
navigator.share({title: 'Title', url: 'https://...'})
```
**Why:** These browser APIs are not supported in the Shop Minis security sandbox. Use the appropriate SDK alternatives when available.
## Extending Rules
To add more component mappings to `prefer-sdk-components`, edit `eslint/rules/prefer-sdk-components.cjs`:
```javascript
const defaultComponents = {
img: 'Image',
button: 'Button',
label: 'Label',
input: 'Input', // Add this
a: 'TransitionLink', // Add this
}
```
All consumers automatically get new rules - no config changes needed!