@despia/local 1.0.0
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/LICENSE +21 -0
- package/README.md +748 -0
- package/generate-offline-manifest.js +34 -0
- package/package.json +95 -0
- package/src/astro.js +51 -0
- package/src/core.js +94 -0
- package/src/esbuild.js +63 -0
- package/src/index.js +16 -0
- package/src/next.js +59 -0
- package/src/nuxt.js +64 -0
- package/src/parcel.js +50 -0
- package/src/remix.js +45 -0
- package/src/rollup.js +45 -0
- package/src/sveltekit.js +45 -0
- package/src/turbopack.js +20 -0
- package/src/vite.js +47 -0
- package/src/webpack.js +56 -0
package/README.md
ADDED
|
@@ -0,0 +1,748 @@
|
|
|
1
|
+
# @despia/local
|
|
2
|
+
|
|
3
|
+
Universal build plugin to generate `despia/local.json` manifest for [Despia](https://despia.com) web-native apps. This manifest enables Despia Local Server, which runs your web app from a secure local HTTPS server on-device, providing full offline functionality with seamless updates.
|
|
4
|
+
|
|
5
|
+
**Note**: Despia Local Server is optional. Normally, the Despia runtime can run your web app directly from a URL. Despia Local Server is for developers who need extra performance and full true native offline support.
|
|
6
|
+
|
|
7
|
+
**Store Compliant**: Despia Local Server is fully compliant with Apple App Store and Google Play Store guidelines. It downloads and caches web content (HTML, CSS, JavaScript) for offline display in a WebView—identical to how web browsers work. No native code or executables are downloaded. See [Store Compliance & Security](#store-compliance--security) section for detailed compliance information.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Universal Support** - Works with Vite, Webpack, Rollup, Next.js, Nuxt, SvelteKit, Astro, Remix, esbuild, Parcel, and more
|
|
12
|
+
- **Zero Dependencies** - Uses only Node.js built-in modules
|
|
13
|
+
- **Automatic Asset Discovery** - Collects all output files (JS, CSS, images, fonts, HTML, etc.)
|
|
14
|
+
- **Root-Relative Paths** - Formats all paths as root-relative (starting with `/`)
|
|
15
|
+
- **Sorted Output** - Alphabetically sorted paths for consistent builds
|
|
16
|
+
- **Standalone Script** - Can be used with any build system via CLI
|
|
17
|
+
|
|
18
|
+
## About Despia Local Server
|
|
19
|
+
|
|
20
|
+
**Note**: Despia Local Server is optional. Normally, the Despia runtime can run your web app directly from a URL. Despia Local Server is for developers who need extra performance and full true native offline support.
|
|
21
|
+
|
|
22
|
+
Despia Local Server enables your web app to run entirely from a secure local HTTPS server on-device, providing a native app experience with full offline functionality.
|
|
23
|
+
|
|
24
|
+
### Architecture
|
|
25
|
+
|
|
26
|
+
The Despia native container consists of:
|
|
27
|
+
|
|
28
|
+
- **Native iOS/Android Container**: The native binary submitted to App Store/Play Store
|
|
29
|
+
- WebView for UI rendering (WKWebView on iOS, WebView on Android)
|
|
30
|
+
- JavaScript bridge for native API access (exposes native capabilities to web content via [`despia-native`](https://www.npmjs.com/package/despia-native))
|
|
31
|
+
- Local HTTPS server infrastructure (runs on-device only)
|
|
32
|
+
- Storage and update management systems
|
|
33
|
+
|
|
34
|
+
- **Web Content Layer** (downloaded and cached):
|
|
35
|
+
- HTML, CSS, JavaScript files
|
|
36
|
+
- Images, fonts, and other static assets
|
|
37
|
+
- Served from `https://localhost` after initial hydration
|
|
38
|
+
|
|
39
|
+
**Key Architecture Principles:**
|
|
40
|
+
- Web content updates (HTML/CSS/JS only)
|
|
41
|
+
- JavaScript bridge provides access to native APIs already in the binary
|
|
42
|
+
- No native code execution or downloads
|
|
43
|
+
- No executable binaries downloaded
|
|
44
|
+
- All native code fixed at App Store/Play Store submission
|
|
45
|
+
|
|
46
|
+
### How It Works
|
|
47
|
+
|
|
48
|
+
**Initial Installation**
|
|
49
|
+
The App Store/Play Store binary contains the native iOS/Android container (see [Architecture](#architecture) above):
|
|
50
|
+
- Native code (Swift/Kotlin) for platform integration
|
|
51
|
+
- WebView component for UI rendering
|
|
52
|
+
- JavaScript bridge for native API access (exposes native capabilities to web content via [`despia-native`](https://www.npmjs.com/package/despia-native))
|
|
53
|
+
- Local HTTPS server infrastructure (runs on-device only)
|
|
54
|
+
- Storage and update management systems
|
|
55
|
+
- No web app assets (HTML, CSS, JavaScript) are bundled in the binary
|
|
56
|
+
|
|
57
|
+
This approach keeps initial install sizes minimal and allows for rapid iteration of web UI without requiring app store resubmissions. **Important**: All native code, the JavaScript bridge, and app functionality are fixed at submission time and cannot be changed. Only web content (HTML/CSS/JS) can be updated.
|
|
58
|
+
|
|
59
|
+
**First Launch Hydration**
|
|
60
|
+
On first app launch, Despia securely downloads your latest web build (HTML, CSS, JavaScript, images, fonts) from your repository and stores it locally on the device. The local HTTPS server then begins serving your web content from `https://localhost`, providing a stable, secure origin for your web application.
|
|
61
|
+
|
|
62
|
+
**Note on Compliance**: This process downloads and caches web content files only. No native code, executables, or binary files are downloaded. The JavaScript executes within the WebView's sandbox, identical to how Safari or Chrome render web pages.
|
|
63
|
+
|
|
64
|
+
**Offline Operation**
|
|
65
|
+
After the initial hydration, your app operates completely offline:
|
|
66
|
+
- Instant app boot times (no network requests)
|
|
67
|
+
- No network connectivity required
|
|
68
|
+
- Full SPA routing and navigation
|
|
69
|
+
- Deep linking support
|
|
70
|
+
- Stable HTTPS origin (`https://localhost`) for security
|
|
71
|
+
|
|
72
|
+
If the device goes offline permanently, the app continues to function using the last hydrated version, ensuring reliability even without connectivity.
|
|
73
|
+
|
|
74
|
+
**Seamless Updates**
|
|
75
|
+
When connectivity is available, Despia checks for updates in the background and downloads new web builds atomically. Updates are applied on the next app launch, ensuring:
|
|
76
|
+
- No partial or broken states
|
|
77
|
+
- Atomic updates for reliability
|
|
78
|
+
- No app store resubmission required for web UI changes
|
|
79
|
+
- Background updates that don't interrupt user experience
|
|
80
|
+
|
|
81
|
+
**Important**: Updates only change web content (HTML, CSS, JavaScript). Native code, permissions, and app functionality remain unchanged from the original App Store/Play Store submission.
|
|
82
|
+
|
|
83
|
+
### Store Compliance & Security
|
|
84
|
+
|
|
85
|
+
**Compliance Summary for App Reviewers:**
|
|
86
|
+
|
|
87
|
+
Despia Local Server downloads and caches web content files (HTML, CSS, JavaScript, images) for offline display in a WebView. This is identical to how web browsers (Safari, Chrome) download and render web pages.
|
|
88
|
+
|
|
89
|
+
**Key Compliance Points:**
|
|
90
|
+
- No native code is downloaded or executed
|
|
91
|
+
- No executable binaries (`.so`, `.dylib`, `.dex`) are downloaded
|
|
92
|
+
- All native code and native API capabilities are fixed at App Store/Play Store submission
|
|
93
|
+
- JavaScript executes only within WebView sandbox (same as browsers)
|
|
94
|
+
- All content served from on-device `localhost` server
|
|
95
|
+
- App permissions and which native APIs are available cannot be changed
|
|
96
|
+
- Web UI can be updated to use existing native APIs (already in binary) in new ways
|
|
97
|
+
|
|
98
|
+
This approach is explicitly permitted under both App Store and Play Store policies, as it involves web content rendering, not native code execution.
|
|
99
|
+
|
|
100
|
+
#### Compliance Validation Tools
|
|
101
|
+
|
|
102
|
+
For conditionally rendering UI elements based on the Despia Native SDK runtime version, consider using [`despia-version-guard`](https://www.npmjs.com/package/despia-version-guard), a recommended tool in the Despia ecosystem. This framework-agnostic solution helps ensure store compliance and reliability by:
|
|
103
|
+
|
|
104
|
+
- Conditionally rendering UI based on `window.bundleNumber` (Despia Native SDK runtime version)
|
|
105
|
+
- Version-gating major UI shifts to maintain store compliance
|
|
106
|
+
- Preventing broken UI when Despia adds new runtime features that older users don't have
|
|
107
|
+
- Supporting React, Vue, Angular, Svelte, and Vanilla JS/Web Components
|
|
108
|
+
|
|
109
|
+
`despia-version-guard` complements `@despia/local` by enabling you to build conditional UI for major version shifts while remaining store-compliant. This is particularly useful for:
|
|
110
|
+
|
|
111
|
+
- **Major UI Shifts**: Show new UI only to users with the required minimum version
|
|
112
|
+
- **Deprecation**: Hide legacy features when version exceeds maximum
|
|
113
|
+
- **Upgrade Prompts**: Conditionally show upgrade buttons based on version
|
|
114
|
+
- **Store Compliance**: Ensure app review compatibility by version-gating major changes
|
|
115
|
+
- **Enterprise Reliability**: Prevent broken UI when Despia adds new runtime features
|
|
116
|
+
|
|
117
|
+
**Installation:**
|
|
118
|
+
```bash
|
|
119
|
+
npm install despia-version-guard
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
**Example Usage (React):**
|
|
123
|
+
```jsx
|
|
124
|
+
import { VersionGuard } from 'despia-version-guard';
|
|
125
|
+
|
|
126
|
+
// Only show this feature if version is 21.0.3 or higher
|
|
127
|
+
<VersionGuard min_version="21.0.3">
|
|
128
|
+
<NewFeatureComponent />
|
|
129
|
+
</VersionGuard>
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
See the [`despia-version-guard` documentation](https://www.npmjs.com/package/despia-version-guard) for framework-specific usage and API details.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
Despia Local Server is fully compliant with both Apple App Store and Google Play Store guidelines. This section explains the compliance in detail.
|
|
137
|
+
|
|
138
|
+
#### Apple App Store Compliance
|
|
139
|
+
|
|
140
|
+
**Guideline 3.3.2 - Code Execution:**
|
|
141
|
+
> "Apps must not download, install, or execute code which introduces or changes features or functionality of the app, including other apps."
|
|
142
|
+
|
|
143
|
+
**Compliance Statement:**
|
|
144
|
+
Despia Local Server complies with this guideline because:
|
|
145
|
+
|
|
146
|
+
1. **No Native Code Execution**: The app does not download, install, or execute any native code (Swift, Objective-C, C++, etc.). All native code is fixed at App Store submission time and cannot be modified.
|
|
147
|
+
|
|
148
|
+
2. **Web Content Rendering Only**: Only web content assets (HTML, CSS, JavaScript) are downloaded and rendered. This is identical to how Safari, Chrome, and other web browsers load and display web content. The JavaScript executes within the WebView's sandbox, not as native code.
|
|
149
|
+
|
|
150
|
+
3. **Fixed Native Functionality**: All native capabilities, permissions, and native API implementations are determined at submission time. The downloaded web content cannot change:
|
|
151
|
+
- App permissions (declared in Info.plist)
|
|
152
|
+
- Which native APIs are available in the binary
|
|
153
|
+
- Native API implementations
|
|
154
|
+
- App Store metadata
|
|
155
|
+
- Binary behavior
|
|
156
|
+
- System integration
|
|
157
|
+
|
|
158
|
+
**However**, the web UI can be updated to use existing native APIs in new ways. For example, if Face ID is already implemented in the native code and exposed through the WebView bridge, the web UI can be updated to call Face ID from new parts of the app. This is compliant because the native capability already exists in the submitted binary—only the web UI's usage of it changes.
|
|
159
|
+
|
|
160
|
+
**Note**: This follows the same pattern as [Expo](https://expo.dev), which has been generally accepted by app stores. However, Apple's interpretation of "feature changes" under Guideline 3.3.2 is subjective, and your app could still face review challenges. It's recommended to be conservative with updates that significantly change app behavior, even when using existing native APIs.
|
|
161
|
+
|
|
162
|
+
4. **On-Device Execution**: All web content is served from a local HTTPS server running on-device (`localhost`). No remote code execution occurs.
|
|
163
|
+
|
|
164
|
+
**This approach is explicitly permitted** under App Store guidelines, as evidenced by:
|
|
165
|
+
- Safari and other browsers downloading and rendering web content
|
|
166
|
+
- Apps using WKWebView to display web content
|
|
167
|
+
- Enterprise apps using web-based UIs
|
|
168
|
+
- Apps that cache web content for offline use
|
|
169
|
+
|
|
170
|
+
#### Google Play Store Compliance
|
|
171
|
+
|
|
172
|
+
**Play Store Policy - Malicious Behavior:**
|
|
173
|
+
> "Apps must not download executable code (e.g., dex, JAR, .so files) from a source other than Google Play."
|
|
174
|
+
|
|
175
|
+
**Compliance Statement:**
|
|
176
|
+
Despia Local Server complies with this policy because:
|
|
177
|
+
|
|
178
|
+
1. **No Executable Code Downloads**: The app does not download any executable code (`.dex`, `.jar`, `.so`, `.apk`, or native binaries). Only web content files (HTML, CSS, JavaScript, images, fonts) are downloaded.
|
|
179
|
+
|
|
180
|
+
2. **Web Content Rendering**: JavaScript files are executed within the WebView's JavaScript engine, not as native executables. This is the same mechanism used by Chrome and other Android browsers.
|
|
181
|
+
|
|
182
|
+
3. **Fixed Native Binary**: The APK/AAB submitted to Google Play contains all native code. No native code is downloaded or executed after installation.
|
|
183
|
+
|
|
184
|
+
4. **Sandboxed Execution**: All web content executes within the WebView's security sandbox, with permissions and capabilities fixed at APK submission time.
|
|
185
|
+
|
|
186
|
+
**However**, the web UI can be updated to use existing native APIs in new ways. For example, if biometric authentication is already implemented in the native code and exposed through the WebView bridge, the web UI can be updated to call it from new parts of the app. This is compliant because the native capability already exists in the submitted APK—only the web UI's usage of it changes.
|
|
187
|
+
|
|
188
|
+
**This approach is explicitly permitted** under Play Store policies, as evidenced by:
|
|
189
|
+
- Chrome and other browsers rendering web content
|
|
190
|
+
- Apps using WebView to display web content
|
|
191
|
+
- Progressive Web Apps (PWAs) that cache content
|
|
192
|
+
- Apps that update UI via web technologies
|
|
193
|
+
|
|
194
|
+
#### Technical Implementation Details
|
|
195
|
+
|
|
196
|
+
**What Gets Downloaded:**
|
|
197
|
+
- HTML files (markup)
|
|
198
|
+
- CSS files (styling)
|
|
199
|
+
- JavaScript files (executed in WebView sandbox)
|
|
200
|
+
- Images, fonts, and other static assets
|
|
201
|
+
- Native code (Swift, Kotlin, C++, etc.) - NOT downloaded
|
|
202
|
+
- Executable binaries (`.so`, `.dylib`, `.dex`, etc.) - NOT downloaded
|
|
203
|
+
- Native libraries or frameworks - NOT downloaded
|
|
204
|
+
|
|
205
|
+
**What Cannot Change:**
|
|
206
|
+
- Native app permissions (declared in Info.plist/AndroidManifest.xml)
|
|
207
|
+
- App Store/Play Store metadata
|
|
208
|
+
- Native API implementations and capabilities
|
|
209
|
+
- System integration capabilities
|
|
210
|
+
- App signing and security model
|
|
211
|
+
- Which native APIs are available in the binary
|
|
212
|
+
|
|
213
|
+
**What CAN Change (Compliant Updates):**
|
|
214
|
+
- **Usage of Existing Native APIs**: The web UI can be updated to use native APIs that are already built into the binary and exposed through the WebView bridge. For example:
|
|
215
|
+
- If Face ID/Touch ID is already implemented in the native code, the web UI can be updated to call it from new parts of the app
|
|
216
|
+
- If camera access is already granted, the web UI can be updated to use it in new features
|
|
217
|
+
- If location services are already available, the web UI can be updated to request location in new screens
|
|
218
|
+
- Any native capability that exists in the submitted binary can be exposed to new parts of the web UI via updates
|
|
219
|
+
|
|
220
|
+
**Important Distinction:**
|
|
221
|
+
- **Compliant**: Updating web content to use existing native APIs (e.g., adding Face ID to a new login screen when Face ID is already in the binary)
|
|
222
|
+
- **Not Compliant**: Downloading new native code or adding new native capabilities that weren't in the original submission
|
|
223
|
+
|
|
224
|
+
This is compliant because:
|
|
225
|
+
1. The native API/capability already exists in the binary submitted to the stores
|
|
226
|
+
2. The WebView bridge was already configured to expose these APIs at submission time
|
|
227
|
+
3. Only the web UI is changing to call existing native functions
|
|
228
|
+
4. No new native code, permissions, or capabilities are being added
|
|
229
|
+
|
|
230
|
+
This follows the same pattern as established frameworks like [Expo](https://expo.dev), which uses JavaScript bridges to expose native APIs to web content and has been generally accepted by app stores.
|
|
231
|
+
|
|
232
|
+
**Important Disclaimer**: While this approach is technically compliant and follows established patterns, Apple's interpretation of Guideline 3.3.2 regarding "feature changes" is subjective. Apps using this pattern have been generally accepted, but your app could still face review challenges, especially if updates significantly change app behavior or functionality. It's recommended to:
|
|
233
|
+
- Be conservative with updates that dramatically change user-facing features
|
|
234
|
+
- Test thoroughly before submitting significant UI/UX changes
|
|
235
|
+
- Be prepared to explain how updates use existing native APIs if questioned during review
|
|
236
|
+
- Consider submitting major feature additions through the normal App Store review process when possible
|
|
237
|
+
|
|
238
|
+
**Security Model:**
|
|
239
|
+
- All web content is served from `https://localhost` (on-device only)
|
|
240
|
+
- WebView sandbox enforces same-origin policy
|
|
241
|
+
- No network access to external servers after initial hydration
|
|
242
|
+
- Content Security Policy (CSP) can be enforced
|
|
243
|
+
- All execution happens within WebView's security boundaries
|
|
244
|
+
|
|
245
|
+
#### Comparison to Similar Technologies
|
|
246
|
+
|
|
247
|
+
This approach is conceptually and technically identical to:
|
|
248
|
+
|
|
249
|
+
1. **Web Browsers** (Safari, Chrome): Download and render web content from servers
|
|
250
|
+
2. **Expo**: Uses JavaScript bridge to expose native APIs to React Native web content
|
|
251
|
+
3. **Electron Apps**: Update web UI content without changing native code
|
|
252
|
+
5. **Progressive Web Apps**: Cache web content for offline use
|
|
253
|
+
6. **Enterprise MDM Solutions**: Deploy web-based UIs to managed devices
|
|
254
|
+
7. **Hybrid Mobile Apps**: Use WebView to display web content
|
|
255
|
+
|
|
256
|
+
The key distinction that ensures compliance: **Web content rendering is not code execution in the App Store/Play Store policy sense**. Native code execution is what the policies restrict, and Despia does not perform any native code execution.
|
|
257
|
+
|
|
258
|
+
**Note**: Expo follows the same pattern of exposing native APIs through JavaScript bridges and has been generally accepted by app stores. However, as with any approach involving web content updates, there's always a possibility of review challenges depending on how Apple interprets "feature changes" in specific cases.
|
|
259
|
+
|
|
260
|
+
#### Practical Example: Using Existing Native APIs
|
|
261
|
+
|
|
262
|
+
To illustrate compliance with using existing native APIs:
|
|
263
|
+
|
|
264
|
+
**Scenario**: An app is submitted with Face ID/Touch ID already implemented in the native code and exposed through the WebView bridge. The initial web UI only uses Face ID on the login screen.
|
|
265
|
+
|
|
266
|
+
**Compliant Update**: The web UI can be updated via OTA to also use Face ID on:
|
|
267
|
+
- A new settings screen
|
|
268
|
+
- A payment confirmation screen
|
|
269
|
+
- Any other new feature
|
|
270
|
+
|
|
271
|
+
**Why This Is Compliant:**
|
|
272
|
+
1. Face ID native code was already in the binary at App Store submission
|
|
273
|
+
2. The WebView bridge was already configured to expose Face ID to JavaScript
|
|
274
|
+
3. Only the web UI JavaScript is changing to call the existing native function
|
|
275
|
+
4. No new native code, permissions, or capabilities are being added
|
|
276
|
+
|
|
277
|
+
**Not Compliant**: Downloading new native code to add Face ID support that wasn't in the original submission.
|
|
278
|
+
|
|
279
|
+
This is analogous to:
|
|
280
|
+
- A web browser using JavaScript to call `navigator.geolocation` in different parts of a website
|
|
281
|
+
- Expo apps updating React Native code to use existing native modules in new screens
|
|
282
|
+
- An Electron app updating its web UI to use existing Electron APIs in new features
|
|
283
|
+
- A hybrid app using existing Cordova plugins in new screens
|
|
284
|
+
|
|
285
|
+
**Review Considerations**: While this pattern is technically compliant and follows established frameworks like Expo, Apple's interpretation of Guideline 3.3.2 can be subjective. If your update significantly changes app behavior or introduces major new user-facing features, consider whether it might be better to submit through the normal App Store review process to avoid potential challenges.
|
|
286
|
+
|
|
287
|
+
#### Developer Responsibility
|
|
288
|
+
|
|
289
|
+
When submitting apps using Despia Local Server:
|
|
290
|
+
|
|
291
|
+
1. **App Store Submission**: Clearly state that the app uses a WebView to display web content that may be cached for offline use. This is standard practice and does not require special disclosure.
|
|
292
|
+
|
|
293
|
+
2. **Play Store Submission**: No special disclosure required. The app uses standard Android WebView components to display web content.
|
|
294
|
+
|
|
295
|
+
3. **Privacy Policy**: If your app collects data through the web content, ensure your privacy policy covers this, as required by both stores.
|
|
296
|
+
|
|
297
|
+
4. **Content Guidelines**: Ensure all web content complies with App Store and Play Store content policies, as the web content is part of your app submission.
|
|
298
|
+
|
|
299
|
+
5. **Update Strategy**: Be mindful that while using existing native APIs in new ways is technically compliant (following the same pattern as Expo), Apple's interpretation of "feature changes" under Guideline 3.3.2 is subjective. Consider:
|
|
300
|
+
- Being conservative with updates that dramatically change app behavior
|
|
301
|
+
- Testing thoroughly before deploying significant UI/UX changes
|
|
302
|
+
- Being prepared to explain how updates use existing native APIs if questioned
|
|
303
|
+
- Submitting major feature additions through normal App Store review when appropriate
|
|
304
|
+
|
|
305
|
+
6. **Version-Based UI Rendering**: Consider using [`despia-version-guard`](https://www.npmjs.com/package/despia-version-guard) to conditionally render UI elements based on the Despia Native SDK runtime version. This helps maintain store compliance by version-gating major changes and prevents broken UI for users with older runtime versions.
|
|
306
|
+
|
|
307
|
+
### The `local.json` Manifest
|
|
308
|
+
|
|
309
|
+
The `despia/local.json` manifest generated by this plugin serves as an asset inventory for Despia Local Server. It contains a complete list of all assets that need to be cached locally, enabling:
|
|
310
|
+
|
|
311
|
+
- **Complete Asset Discovery**: Ensures all files (JS, CSS, images, fonts, HTML) are properly cached
|
|
312
|
+
- **Efficient Updates**: Allows Despia to determine which assets have changed between builds
|
|
313
|
+
- **Reliable Offline Operation**: Guarantees all required assets are available offline
|
|
314
|
+
- **Atomic Updates**: Enables safe, complete updates without partial states
|
|
315
|
+
|
|
316
|
+
This manifest is automatically used by Despia during the hydration and update processes to ensure your app has everything it needs to run offline.
|
|
317
|
+
|
|
318
|
+
## Installation
|
|
319
|
+
|
|
320
|
+
```bash
|
|
321
|
+
npm install --save-dev @despia/local
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
## Quick Start
|
|
325
|
+
|
|
326
|
+
### Option 1: Framework-Specific Plugin (Recommended)
|
|
327
|
+
|
|
328
|
+
Choose the plugin for your framework below.
|
|
329
|
+
|
|
330
|
+
### Option 2: Standalone Script (Universal)
|
|
331
|
+
|
|
332
|
+
Add to your `package.json`:
|
|
333
|
+
|
|
334
|
+
```json
|
|
335
|
+
{
|
|
336
|
+
"scripts": {
|
|
337
|
+
"build": "your-build-command",
|
|
338
|
+
"postbuild": "despia-local"
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
Or run manually:
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
npx despia-local [outputDir] [entryHtml]
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
## Framework Support
|
|
350
|
+
|
|
351
|
+
### Vite
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
// vite.config.js
|
|
355
|
+
import { defineConfig } from 'vite';
|
|
356
|
+
import { despiaOfflinePlugin } from '@despia/local/vite';
|
|
357
|
+
|
|
358
|
+
export default defineConfig({
|
|
359
|
+
plugins: [
|
|
360
|
+
// ... your other plugins
|
|
361
|
+
despiaOfflinePlugin({
|
|
362
|
+
outDir: 'dist', // optional, default: 'dist'
|
|
363
|
+
entryHtml: 'index.html' // optional, default: 'index.html'
|
|
364
|
+
})
|
|
365
|
+
]
|
|
366
|
+
});
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
**Works with:**
|
|
370
|
+
- Vite + React
|
|
371
|
+
- Vite + Vue
|
|
372
|
+
- Vite + Svelte
|
|
373
|
+
- Vite + Preact
|
|
374
|
+
- Vite + Lit
|
|
375
|
+
- Any Vite-based project
|
|
376
|
+
|
|
377
|
+
### Webpack
|
|
378
|
+
|
|
379
|
+
```javascript
|
|
380
|
+
// webpack.config.js
|
|
381
|
+
const DespiaOfflinePlugin = require('@despia/local/webpack');
|
|
382
|
+
|
|
383
|
+
module.exports = {
|
|
384
|
+
// ... your config
|
|
385
|
+
plugins: [
|
|
386
|
+
// ... your other plugins
|
|
387
|
+
new DespiaOfflinePlugin({
|
|
388
|
+
outDir: 'dist', // optional, default: 'dist'
|
|
389
|
+
entryHtml: 'index.html' // optional, default: 'index.html'
|
|
390
|
+
})
|
|
391
|
+
]
|
|
392
|
+
};
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
**Works with:**
|
|
396
|
+
- Create React App (eject or CRACO)
|
|
397
|
+
- Vue CLI
|
|
398
|
+
- Angular (custom webpack config)
|
|
399
|
+
- Any webpack-based project
|
|
400
|
+
|
|
401
|
+
### Rollup
|
|
402
|
+
|
|
403
|
+
```javascript
|
|
404
|
+
// rollup.config.js
|
|
405
|
+
import { despiaOffline } from '@despia/local/rollup';
|
|
406
|
+
|
|
407
|
+
export default {
|
|
408
|
+
// ... your config
|
|
409
|
+
plugins: [
|
|
410
|
+
// ... your other plugins
|
|
411
|
+
despiaOffline({
|
|
412
|
+
outDir: 'dist',
|
|
413
|
+
entryHtml: 'index.html'
|
|
414
|
+
})
|
|
415
|
+
]
|
|
416
|
+
};
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
### Next.js
|
|
420
|
+
|
|
421
|
+
```javascript
|
|
422
|
+
// next.config.js
|
|
423
|
+
const withDespiaOffline = require('@despia/local/next');
|
|
424
|
+
|
|
425
|
+
module.exports = withDespiaOffline({
|
|
426
|
+
entryHtml: 'index.html',
|
|
427
|
+
outDir: '.next' // or 'out' for static export
|
|
428
|
+
})({
|
|
429
|
+
// your Next.js config
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**For static export:**
|
|
434
|
+
|
|
435
|
+
```javascript
|
|
436
|
+
// next.config.js
|
|
437
|
+
const withDespiaOffline = require('@despia/local/next');
|
|
438
|
+
|
|
439
|
+
module.exports = withDespiaOffline({
|
|
440
|
+
outDir: 'out', // Next.js static export directory
|
|
441
|
+
entryHtml: 'index.html'
|
|
442
|
+
})({
|
|
443
|
+
output: 'export',
|
|
444
|
+
// ... rest of config
|
|
445
|
+
});
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
**Alternative: Webpack Plugin Approach**
|
|
449
|
+
|
|
450
|
+
```javascript
|
|
451
|
+
// next.config.js
|
|
452
|
+
const DespiaOfflinePlugin = require('@despia/local/webpack');
|
|
453
|
+
|
|
454
|
+
module.exports = {
|
|
455
|
+
webpack: (config) => {
|
|
456
|
+
config.plugins.push(
|
|
457
|
+
new DespiaOfflinePlugin({ outDir: '.next' })
|
|
458
|
+
);
|
|
459
|
+
return config;
|
|
460
|
+
}
|
|
461
|
+
};
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Nuxt
|
|
465
|
+
|
|
466
|
+
```javascript
|
|
467
|
+
// nuxt.config.js
|
|
468
|
+
export default {
|
|
469
|
+
modules: ['@despia/local/nuxt'],
|
|
470
|
+
despiaOffline: {
|
|
471
|
+
entryHtml: 'index.html'
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
**Or as a local module:**
|
|
477
|
+
|
|
478
|
+
```javascript
|
|
479
|
+
// modules/despia-offline.js
|
|
480
|
+
import DespiaOfflineModule from '@despia/local/nuxt';
|
|
481
|
+
export default DespiaOfflineModule;
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
### SvelteKit
|
|
485
|
+
|
|
486
|
+
```javascript
|
|
487
|
+
// vite.config.js
|
|
488
|
+
import { sveltekit } from '@sveltejs/kit/vite';
|
|
489
|
+
import { despiaOfflineSvelteKit } from '@despia/local/sveltekit';
|
|
490
|
+
|
|
491
|
+
export default {
|
|
492
|
+
plugins: [
|
|
493
|
+
sveltekit(),
|
|
494
|
+
despiaOfflineSvelteKit({
|
|
495
|
+
entryHtml: 'index.html'
|
|
496
|
+
})
|
|
497
|
+
]
|
|
498
|
+
};
|
|
499
|
+
```
|
|
500
|
+
|
|
501
|
+
### Astro
|
|
502
|
+
|
|
503
|
+
```javascript
|
|
504
|
+
// astro.config.mjs
|
|
505
|
+
import { defineConfig } from 'astro/config';
|
|
506
|
+
import despiaOffline from '@despia/local/astro';
|
|
507
|
+
|
|
508
|
+
export default defineConfig({
|
|
509
|
+
integrations: [
|
|
510
|
+
despiaOffline({
|
|
511
|
+
entryHtml: 'index.html',
|
|
512
|
+
outDir: 'dist'
|
|
513
|
+
})
|
|
514
|
+
]
|
|
515
|
+
});
|
|
516
|
+
```
|
|
517
|
+
|
|
518
|
+
### Remix
|
|
519
|
+
|
|
520
|
+
```javascript
|
|
521
|
+
// vite.config.js (Remix uses Vite)
|
|
522
|
+
import { remix } from '@remix-run/dev';
|
|
523
|
+
import { despiaOfflineRemix } from '@despia/local/remix';
|
|
524
|
+
|
|
525
|
+
export default {
|
|
526
|
+
plugins: [
|
|
527
|
+
remix(),
|
|
528
|
+
despiaOfflineRemix({
|
|
529
|
+
entryHtml: 'index.html',
|
|
530
|
+
outDir: 'build/client'
|
|
531
|
+
})
|
|
532
|
+
]
|
|
533
|
+
};
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
### esbuild
|
|
537
|
+
|
|
538
|
+
```javascript
|
|
539
|
+
import { build } from 'esbuild';
|
|
540
|
+
import { despiaOfflineEsbuild } from '@despia/local/esbuild';
|
|
541
|
+
|
|
542
|
+
await build({
|
|
543
|
+
entryPoints: ['src/index.js'],
|
|
544
|
+
outdir: 'dist',
|
|
545
|
+
plugins: [
|
|
546
|
+
despiaOfflineEsbuild({
|
|
547
|
+
outDir: 'dist',
|
|
548
|
+
entryHtml: 'index.html'
|
|
549
|
+
})
|
|
550
|
+
]
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
### Parcel
|
|
555
|
+
|
|
556
|
+
```json
|
|
557
|
+
// .parcelrc
|
|
558
|
+
{
|
|
559
|
+
"extends": "@parcel/config-default",
|
|
560
|
+
"plugins": {
|
|
561
|
+
"@despia/local/parcel": true
|
|
562
|
+
}
|
|
563
|
+
}
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Or in package.json:**
|
|
567
|
+
|
|
568
|
+
```json
|
|
569
|
+
{
|
|
570
|
+
"parcel": {
|
|
571
|
+
"plugins": {
|
|
572
|
+
"@despia/local/parcel": true
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
```
|
|
577
|
+
|
|
578
|
+
### Other Build Tools
|
|
579
|
+
|
|
580
|
+
For any build tool not listed above, use the standalone CLI script:
|
|
581
|
+
|
|
582
|
+
```json
|
|
583
|
+
{
|
|
584
|
+
"scripts": {
|
|
585
|
+
"build": "your-build-command",
|
|
586
|
+
"postbuild": "despia-local dist index.html"
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
```
|
|
590
|
+
|
|
591
|
+
## Configuration Options
|
|
592
|
+
|
|
593
|
+
All plugins accept the following options:
|
|
594
|
+
|
|
595
|
+
| Option | Type | Default | Description |
|
|
596
|
+
|--------|------|---------|-------------|
|
|
597
|
+
| `outDir` | `string` | `'dist'` | Output directory to scan for assets |
|
|
598
|
+
| `entryHtml` | `string` | `'index.html'` | Entry HTML filename to include in manifest |
|
|
599
|
+
|
|
600
|
+
## Output Format
|
|
601
|
+
|
|
602
|
+
The generated `despia/local.json` file contains a sorted JSON array of root-relative paths:
|
|
603
|
+
|
|
604
|
+
```json
|
|
605
|
+
[
|
|
606
|
+
"/assets/app.abc123.css",
|
|
607
|
+
"/assets/app.def456.js",
|
|
608
|
+
"/assets/logo.xyz789.png",
|
|
609
|
+
"/index.html"
|
|
610
|
+
]
|
|
611
|
+
```
|
|
612
|
+
|
|
613
|
+
## Examples
|
|
614
|
+
|
|
615
|
+
### React + Vite
|
|
616
|
+
|
|
617
|
+
```javascript
|
|
618
|
+
// vite.config.js
|
|
619
|
+
import { defineConfig } from 'vite';
|
|
620
|
+
import react from '@vitejs/plugin-react';
|
|
621
|
+
import { despiaOfflinePlugin } from '@despia/local/vite';
|
|
622
|
+
|
|
623
|
+
export default defineConfig({
|
|
624
|
+
plugins: [
|
|
625
|
+
react(),
|
|
626
|
+
despiaOfflinePlugin()
|
|
627
|
+
]
|
|
628
|
+
});
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
### Vue + Vite
|
|
632
|
+
|
|
633
|
+
```javascript
|
|
634
|
+
// vite.config.js
|
|
635
|
+
import { defineConfig } from 'vite';
|
|
636
|
+
import vue from '@vitejs/plugin-vue';
|
|
637
|
+
import { despiaOfflinePlugin } from '@despia/local/vite';
|
|
638
|
+
|
|
639
|
+
export default defineConfig({
|
|
640
|
+
plugins: [
|
|
641
|
+
vue(),
|
|
642
|
+
despiaOfflinePlugin()
|
|
643
|
+
]
|
|
644
|
+
});
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Angular
|
|
648
|
+
|
|
649
|
+
```javascript
|
|
650
|
+
// angular.json - Add to build configuration
|
|
651
|
+
{
|
|
652
|
+
"projects": {
|
|
653
|
+
"your-app": {
|
|
654
|
+
"architect": {
|
|
655
|
+
"build": {
|
|
656
|
+
"builder": "@angular-builders/custom-webpack:browser",
|
|
657
|
+
"options": {
|
|
658
|
+
"customWebpackConfig": {
|
|
659
|
+
"path": "./webpack.config.js"
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
```
|
|
668
|
+
|
|
669
|
+
```javascript
|
|
670
|
+
// webpack.config.js
|
|
671
|
+
const DespiaOfflinePlugin = require('@despia/local/webpack');
|
|
672
|
+
|
|
673
|
+
module.exports = {
|
|
674
|
+
plugins: [
|
|
675
|
+
new DespiaOfflinePlugin({ outDir: 'dist' })
|
|
676
|
+
]
|
|
677
|
+
};
|
|
678
|
+
```
|
|
679
|
+
|
|
680
|
+
### Svelte + Vite
|
|
681
|
+
|
|
682
|
+
```javascript
|
|
683
|
+
// vite.config.js
|
|
684
|
+
import { defineConfig } from 'vite';
|
|
685
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte';
|
|
686
|
+
import { despiaOfflinePlugin } from '@despia/local/vite';
|
|
687
|
+
|
|
688
|
+
export default defineConfig({
|
|
689
|
+
plugins: [
|
|
690
|
+
svelte(),
|
|
691
|
+
despiaOfflinePlugin()
|
|
692
|
+
]
|
|
693
|
+
});
|
|
694
|
+
```
|
|
695
|
+
|
|
696
|
+
## How the Plugin Works
|
|
697
|
+
|
|
698
|
+
1. **Hook into Build Process** - The plugin hooks into your build tool's completion event
|
|
699
|
+
2. **Scan Output Directory** - Recursively scans the build output directory for all files
|
|
700
|
+
3. **Collect Asset Paths** - Collects paths from both the build tool's bundle metadata and file system
|
|
701
|
+
4. **Normalize Paths** - Converts all paths to root-relative format (starting with `/`)
|
|
702
|
+
5. **Include Entry HTML** - Ensures the entry HTML file is always included
|
|
703
|
+
6. **Sort & Write** - Sorts paths alphabetically and writes to `despia/local.json`
|
|
704
|
+
|
|
705
|
+
The generated manifest is then used by Despia during app hydration and updates to ensure all assets are properly cached for offline operation.
|
|
706
|
+
|
|
707
|
+
## Notes
|
|
708
|
+
|
|
709
|
+
- The plugin automatically creates the `despia` subdirectory if it doesn't exist
|
|
710
|
+
- All paths are normalized to use forward slashes (`/`) regardless of OS
|
|
711
|
+
- Paths are sorted alphabetically for consistent output across builds
|
|
712
|
+
- The entry HTML file is always included, even if not found in the output directory
|
|
713
|
+
- The `despia` directory itself is excluded from the manifest to avoid recursion
|
|
714
|
+
|
|
715
|
+
## Troubleshooting
|
|
716
|
+
|
|
717
|
+
### Manifest not generated
|
|
718
|
+
|
|
719
|
+
1. Ensure the build completes successfully
|
|
720
|
+
2. Check that the output directory exists
|
|
721
|
+
3. Verify the `outDir` option matches your build configuration
|
|
722
|
+
4. Check console for error messages
|
|
723
|
+
|
|
724
|
+
### Missing assets in manifest
|
|
725
|
+
|
|
726
|
+
- The plugin scans the entire output directory
|
|
727
|
+
- Ensure assets are copied to the output directory during build
|
|
728
|
+
- Check that file paths are correctly formatted
|
|
729
|
+
|
|
730
|
+
### Path format issues
|
|
731
|
+
|
|
732
|
+
- All paths are automatically normalized to root-relative format
|
|
733
|
+
- Paths starting with `/` are preserved as-is
|
|
734
|
+
- Windows backslashes are converted to forward slashes
|
|
735
|
+
|
|
736
|
+
## Contributing
|
|
737
|
+
|
|
738
|
+
Contributions welcome! Please open an issue or submit a pull request.
|
|
739
|
+
|
|
740
|
+
## License
|
|
741
|
+
|
|
742
|
+
MIT
|
|
743
|
+
|
|
744
|
+
## Related
|
|
745
|
+
|
|
746
|
+
- [Despia](https://despia.com) - Web-native app platform
|
|
747
|
+
- [despia-native](https://www.npmjs.com/package/despia-native) - JavaScript SDK for accessing native device features (haptics, biometrics, camera, location, push notifications, in-app purchases, and 25+ more APIs)
|
|
748
|
+
- [despia-version-guard](https://www.npmjs.com/package/despia-version-guard) - Framework-agnostic solution for conditionally rendering UI based on Despia Native SDK runtime version
|