@despia/local 1.0.1 → 1.0.2
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 +77 -4
- package/generate-offline-manifest.js +66 -6
- package/package.json +1 -1
- package/src/core.js +9 -6
- package/src/next.js +35 -10
- package/src/webpack.js +16 -1
package/README.md
CHANGED
|
@@ -418,19 +418,22 @@ export default {
|
|
|
418
418
|
|
|
419
419
|
### Next.js
|
|
420
420
|
|
|
421
|
+
**For static export (recommended for static sites):**
|
|
422
|
+
|
|
421
423
|
```javascript
|
|
422
424
|
// next.config.js
|
|
423
425
|
const withDespiaLocal = require('@despia/local/next');
|
|
424
426
|
|
|
425
427
|
module.exports = withDespiaLocal({
|
|
426
428
|
entryHtml: 'index.html',
|
|
427
|
-
outDir: '
|
|
429
|
+
outDir: 'out' // Next.js static export directory
|
|
428
430
|
})({
|
|
431
|
+
output: 'export',
|
|
429
432
|
// your Next.js config
|
|
430
433
|
});
|
|
431
434
|
```
|
|
432
435
|
|
|
433
|
-
**For static export:**
|
|
436
|
+
**For static export (alternative):**
|
|
434
437
|
|
|
435
438
|
```javascript
|
|
436
439
|
// next.config.js
|
|
@@ -445,22 +448,68 @@ module.exports = withDespiaLocal({
|
|
|
445
448
|
});
|
|
446
449
|
```
|
|
447
450
|
|
|
448
|
-
**Alternative: Webpack Plugin Approach
|
|
451
|
+
**Alternative: Webpack Plugin Approach (works best for static export):**
|
|
449
452
|
|
|
450
453
|
```javascript
|
|
451
454
|
// next.config.js
|
|
452
455
|
const DespiaLocalPlugin = require('@despia/local/webpack');
|
|
453
456
|
|
|
454
457
|
module.exports = {
|
|
458
|
+
output: 'export', // For static export
|
|
455
459
|
webpack: (config) => {
|
|
456
460
|
config.plugins.push(
|
|
457
|
-
new DespiaLocalPlugin({ outDir: '
|
|
461
|
+
new DespiaLocalPlugin({ outDir: 'out' })
|
|
458
462
|
);
|
|
459
463
|
return config;
|
|
460
464
|
}
|
|
461
465
|
};
|
|
462
466
|
```
|
|
463
467
|
|
|
468
|
+
**Note**: The webpack plugin approach works best for static export. For SSR apps, use the post-build script approach described below.
|
|
469
|
+
|
|
470
|
+
**For SSR (Server-Side Rendering) apps:**
|
|
471
|
+
|
|
472
|
+
SSR Next.js apps require special handling because:
|
|
473
|
+
- Client assets are in `.next/static/` directory (not `.next/`)
|
|
474
|
+
- No static HTML files exist (pages are server-rendered)
|
|
475
|
+
- Manifest should only include client-side assets (JS, CSS, images)
|
|
476
|
+
- Server-side code in `.next/server/` should NOT be included
|
|
477
|
+
|
|
478
|
+
**Recommended approach for SSR (most reliable):**
|
|
479
|
+
|
|
480
|
+
Use a post-build script in your `package.json`:
|
|
481
|
+
|
|
482
|
+
```json
|
|
483
|
+
{
|
|
484
|
+
"scripts": {
|
|
485
|
+
"build": "next build",
|
|
486
|
+
"postbuild": "despia-local .next/static"
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
```
|
|
490
|
+
|
|
491
|
+
This approach:
|
|
492
|
+
- Runs after Next.js build completes
|
|
493
|
+
- Targets `.next/static/` where all client assets are stored
|
|
494
|
+
- Works reliably for both SSR and static export
|
|
495
|
+
- No `entryHtml` needed (SSR apps don't have static HTML files)
|
|
496
|
+
|
|
497
|
+
**Alternative: Using the plugin (may have timing issues):**
|
|
498
|
+
|
|
499
|
+
```javascript
|
|
500
|
+
// next.config.js
|
|
501
|
+
const withDespiaLocal = require('@despia/local/next');
|
|
502
|
+
|
|
503
|
+
module.exports = withDespiaLocal({
|
|
504
|
+
// entryHtml is optional for SSR (not used)
|
|
505
|
+
outDir: '.next/static' // Target client assets directory
|
|
506
|
+
})({
|
|
507
|
+
// your Next.js config (SSR mode - no output: 'export')
|
|
508
|
+
});
|
|
509
|
+
```
|
|
510
|
+
|
|
511
|
+
**Note**: The plugin approach may not work reliably for SSR because Next.js doesn't provide a reliable build completion hook. The post-build script approach is recommended for SSR apps.
|
|
512
|
+
|
|
464
513
|
### Nuxt
|
|
465
514
|
|
|
466
515
|
```javascript
|
|
@@ -733,6 +782,30 @@ The generated manifest is then used by Despia during app hydration and updates t
|
|
|
733
782
|
- Paths starting with `/` are preserved as-is
|
|
734
783
|
- Windows backslashes are converted to forward slashes
|
|
735
784
|
|
|
785
|
+
### Next.js SSR Issues
|
|
786
|
+
|
|
787
|
+
**Manifest not generated for SSR app:**
|
|
788
|
+
|
|
789
|
+
1. Ensure you're targeting `.next/static/` directory (not `.next/`)
|
|
790
|
+
2. Use the post-build script approach: `"postbuild": "despia-local .next/static"`
|
|
791
|
+
3. Verify build completed successfully: `next build` should finish without errors
|
|
792
|
+
4. Check that `.next/static/` directory exists after build
|
|
793
|
+
5. The plugin approach may not work reliably for SSR - prefer post-build script
|
|
794
|
+
|
|
795
|
+
**Missing assets in SSR manifest:**
|
|
796
|
+
|
|
797
|
+
- SSR apps only need client assets (JS, CSS, images)
|
|
798
|
+
- Server-side code in `.next/server/` is NOT included (correct behavior)
|
|
799
|
+
- Only assets in `.next/static/` should be in the manifest
|
|
800
|
+
- Verify you're scanning `.next/static/` not `.next/` or `.next/server/`
|
|
801
|
+
|
|
802
|
+
**Wrong directory for SSR:**
|
|
803
|
+
|
|
804
|
+
If you see errors about missing directories:
|
|
805
|
+
- For SSR: Use `.next/static/` (client assets only)
|
|
806
|
+
- For static export: Use `out/` (full static site)
|
|
807
|
+
- Never use `.next/server/` (server code, not needed for manifest)
|
|
808
|
+
|
|
736
809
|
## Contributing
|
|
737
810
|
|
|
738
811
|
Contributions welcome! Please open an issue or submit a pull request.
|
|
@@ -14,21 +14,81 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { generateManifest } from './src/core.js';
|
|
17
|
-
import { resolve } from 'path';
|
|
17
|
+
import { resolve, join } from 'path';
|
|
18
|
+
import { existsSync } from 'fs';
|
|
18
19
|
|
|
19
20
|
// Get command line arguments
|
|
20
21
|
const outputDir = process.argv[2] || 'dist';
|
|
21
22
|
const entryHtml = process.argv[3] || 'index.html';
|
|
22
23
|
|
|
24
|
+
// Detect Next.js SSR context
|
|
25
|
+
const isNextJsDir = outputDir.includes('.next');
|
|
26
|
+
const isNextJsStatic = outputDir.includes('.next/static') || outputDir === '.next/static';
|
|
27
|
+
const isNextJsRoot = outputDir === '.next';
|
|
28
|
+
const isNextJsServer = outputDir.includes('.next/server');
|
|
29
|
+
|
|
30
|
+
// Determine if we should skip entryHtml (for SSR apps)
|
|
31
|
+
const skipEntryHtml = isNextJsStatic || isNextJsServer;
|
|
32
|
+
|
|
33
|
+
// Provide helpful messages for Next.js
|
|
34
|
+
if (isNextJsDir && !isNextJsStatic && !isNextJsServer) {
|
|
35
|
+
if (isNextJsRoot) {
|
|
36
|
+
console.warn('⚠ Warning: Scanning .next/ directory directly.');
|
|
37
|
+
console.warn('💡 For SSR apps, use: despia-local .next/static');
|
|
38
|
+
console.warn('💡 For static export, use: despia-local out');
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
if (isNextJsServer) {
|
|
43
|
+
console.error('❌ Error: .next/server/ contains server-side code, not client assets.');
|
|
44
|
+
console.error('💡 For SSR apps, use: despia-local .next/static');
|
|
45
|
+
process.exit(1);
|
|
46
|
+
}
|
|
47
|
+
|
|
23
48
|
try {
|
|
24
|
-
|
|
25
|
-
|
|
49
|
+
const resolvedPath = resolve(process.cwd(), outputDir);
|
|
50
|
+
console.log(`Scanning ${resolvedPath} for assets...`);
|
|
51
|
+
|
|
52
|
+
// Check if directory exists, and provide helpful suggestions for Next.js
|
|
53
|
+
if (!existsSync(resolvedPath)) {
|
|
54
|
+
if (isNextJsRoot) {
|
|
55
|
+
console.error(`❌ Directory "${resolvedPath}" does not exist.`);
|
|
56
|
+
console.error('💡 For SSR apps, try: despia-local .next/static');
|
|
57
|
+
console.error('💡 For static export, try: despia-local out');
|
|
58
|
+
}
|
|
59
|
+
throw new Error(`Output directory "${resolvedPath}" does not exist.`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Check for Next.js static directory and suggest if scanning wrong location
|
|
63
|
+
if (isNextJsRoot && existsSync(join(resolvedPath, 'static'))) {
|
|
64
|
+
console.warn('⚠ Found .next/static/ subdirectory.');
|
|
65
|
+
console.warn('💡 For SSR apps, consider scanning .next/static directly for better results.');
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const paths = generateManifest({
|
|
69
|
+
outputDir,
|
|
70
|
+
entryHtml,
|
|
71
|
+
skipEntryHtml
|
|
72
|
+
});
|
|
26
73
|
|
|
27
74
|
console.log(`✓ Generated despia/local.json`);
|
|
28
75
|
console.log(`✓ Included ${paths.length} assets`);
|
|
29
|
-
|
|
76
|
+
if (!skipEntryHtml) {
|
|
77
|
+
console.log(`✓ Entry HTML: /${entryHtml}`);
|
|
78
|
+
} else {
|
|
79
|
+
console.log(`✓ Skipped entry HTML (SSR mode)`);
|
|
80
|
+
}
|
|
30
81
|
} catch (error) {
|
|
31
|
-
console.error(
|
|
32
|
-
console.error('Please run this script after your build completes.');
|
|
82
|
+
console.error(`❌ Error: ${error.message}`);
|
|
83
|
+
console.error('💡 Please run this script after your build completes.');
|
|
84
|
+
|
|
85
|
+
// Additional help for Next.js
|
|
86
|
+
if (isNextJsDir) {
|
|
87
|
+
console.error('');
|
|
88
|
+
console.error('Next.js tips:');
|
|
89
|
+
console.error(' - SSR apps: Use "despia-local .next/static"');
|
|
90
|
+
console.error(' - Static export: Use "despia-local out"');
|
|
91
|
+
}
|
|
92
|
+
|
|
33
93
|
process.exit(1);
|
|
34
94
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@despia/local",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Universal build plugin to generate despia/local.json manifest for offline caching in Despia web-native apps. Supports Vite, Webpack, Rollup, Next.js, Nuxt, SvelteKit, Astro, Remix, esbuild, Parcel, and more.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/core.js",
|
package/src/core.js
CHANGED
|
@@ -51,9 +51,10 @@ export function collectFiles(dir, baseDir = dir) {
|
|
|
51
51
|
* @param {string} options.outputDir - Output directory path
|
|
52
52
|
* @param {string} options.entryHtml - Entry HTML filename (default: 'index.html')
|
|
53
53
|
* @param {string[]} options.additionalPaths - Additional paths to include
|
|
54
|
+
* @param {boolean} options.skipEntryHtml - Skip adding entry HTML to manifest (for SSR apps)
|
|
54
55
|
* @returns {string[]} Array of all asset paths
|
|
55
56
|
*/
|
|
56
|
-
export function generateManifest({ outputDir, entryHtml = 'index.html', additionalPaths = [] }) {
|
|
57
|
+
export function generateManifest({ outputDir, entryHtml = 'index.html', additionalPaths = [], skipEntryHtml = false }) {
|
|
57
58
|
const outputPath = resolve(process.cwd(), outputDir);
|
|
58
59
|
const manifestPath = join(outputPath, 'despia', 'local.json');
|
|
59
60
|
|
|
@@ -71,11 +72,13 @@ export function generateManifest({ outputDir, entryHtml = 'index.html', addition
|
|
|
71
72
|
assetPaths.add(normalizedPath);
|
|
72
73
|
});
|
|
73
74
|
|
|
74
|
-
// Ensure entry HTML is included
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
75
|
+
// Ensure entry HTML is included (unless skipped for SSR apps)
|
|
76
|
+
if (!skipEntryHtml) {
|
|
77
|
+
const entryPath = entryHtml.startsWith('/')
|
|
78
|
+
? entryHtml
|
|
79
|
+
: '/' + entryHtml;
|
|
80
|
+
assetPaths.add(entryPath);
|
|
81
|
+
}
|
|
79
82
|
|
|
80
83
|
// Convert to sorted array
|
|
81
84
|
const sortedPaths = Array.from(assetPaths).sort();
|
package/src/next.js
CHANGED
|
@@ -1,16 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Next.js integration for generating despia/local.json manifest
|
|
3
3
|
*
|
|
4
|
-
* Usage
|
|
4
|
+
* Usage for static export:
|
|
5
5
|
* const withDespiaLocal = require('@despia/local/next');
|
|
6
6
|
* module.exports = withDespiaLocal({
|
|
7
7
|
* entryHtml: 'index.html',
|
|
8
|
-
* outDir: '
|
|
8
|
+
* outDir: 'out' // Next.js static export directory
|
|
9
9
|
* })({
|
|
10
|
+
* output: 'export',
|
|
10
11
|
* // your next config
|
|
11
12
|
* });
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
+
* Usage for SSR (recommended: use post-build script):
|
|
15
|
+
* // package.json
|
|
16
|
+
* {
|
|
17
|
+
* "scripts": {
|
|
18
|
+
* "build": "next build",
|
|
19
|
+
* "postbuild": "despia-local .next/static"
|
|
20
|
+
* }
|
|
21
|
+
* }
|
|
22
|
+
*
|
|
23
|
+
* Or use the webpack plugin approach (works best for static export):
|
|
14
24
|
* const DespiaLocalPlugin = require('@despia/local/webpack');
|
|
15
25
|
* module.exports = {
|
|
16
26
|
* webpack: (config) => {
|
|
@@ -31,18 +41,33 @@ export function withDespiaLocal(pluginOptions = {}) {
|
|
|
31
41
|
};
|
|
32
42
|
|
|
33
43
|
return (nextConfig = {}) => {
|
|
44
|
+
// Detect if this is static export or SSR
|
|
45
|
+
const isStaticExport = nextConfig.output === 'export';
|
|
34
46
|
const existingWebpack = nextConfig.webpack;
|
|
35
47
|
|
|
36
48
|
return {
|
|
37
49
|
...nextConfig,
|
|
38
50
|
webpack: (config, options) => {
|
|
39
|
-
//
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
51
|
+
// Only add webpack plugin for client builds (not server builds)
|
|
52
|
+
// For SSR, the webpack plugin will target .next/static during client build
|
|
53
|
+
// For static export, it works normally
|
|
54
|
+
if (!options.isServer) {
|
|
55
|
+
// Determine the correct output directory
|
|
56
|
+
let targetOutDir = localConfig.outDir;
|
|
57
|
+
|
|
58
|
+
// For SSR apps, target .next/static where client assets are stored
|
|
59
|
+
if (!isStaticExport && targetOutDir === '.next') {
|
|
60
|
+
targetOutDir = '.next/static';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
config.plugins.push(
|
|
64
|
+
new DespiaLocalPlugin({
|
|
65
|
+
outDir: targetOutDir,
|
|
66
|
+
entryHtml: localConfig.entryHtml,
|
|
67
|
+
skipEntryHtml: !isStaticExport // Skip entryHtml for SSR
|
|
68
|
+
})
|
|
69
|
+
);
|
|
70
|
+
}
|
|
46
71
|
|
|
47
72
|
// Call existing webpack config if present
|
|
48
73
|
if (typeof existingWebpack === 'function') {
|
package/src/webpack.js
CHANGED
|
@@ -9,6 +9,7 @@ class DespiaLocalPlugin {
|
|
|
9
9
|
this.options = {
|
|
10
10
|
outDir: options.outDir || 'dist',
|
|
11
11
|
entryHtml: options.entryHtml || 'index.html',
|
|
12
|
+
skipEntryHtml: options.skipEntryHtml || false,
|
|
12
13
|
...options
|
|
13
14
|
};
|
|
14
15
|
}
|
|
@@ -17,6 +18,19 @@ class DespiaLocalPlugin {
|
|
|
17
18
|
const pluginName = 'DespiaLocalPlugin';
|
|
18
19
|
|
|
19
20
|
compiler.hooks.afterEmit.tapAsync(pluginName, (compilation, callback) => {
|
|
21
|
+
// Detect if this is a Next.js server build
|
|
22
|
+
// Next.js server builds have specific compiler name patterns
|
|
23
|
+
const compilerName = compilation.compiler.name || '';
|
|
24
|
+
const isNextJsServerBuild = compilerName.includes('server') ||
|
|
25
|
+
compilerName.includes('Server') ||
|
|
26
|
+
compilation.compiler.options?.target === 'node';
|
|
27
|
+
|
|
28
|
+
// Skip manifest generation for server builds (SSR apps don't need server-side assets)
|
|
29
|
+
if (isNextJsServerBuild) {
|
|
30
|
+
callback();
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
20
34
|
// Get output path from webpack compiler
|
|
21
35
|
const outputPath = compilation.compiler.outputPath || this.options.outDir;
|
|
22
36
|
const additionalPaths = [];
|
|
@@ -41,7 +55,8 @@ class DespiaLocalPlugin {
|
|
|
41
55
|
const paths = generateManifest({
|
|
42
56
|
outputDir: outputPath,
|
|
43
57
|
entryHtml: this.options.entryHtml,
|
|
44
|
-
additionalPaths
|
|
58
|
+
additionalPaths,
|
|
59
|
+
skipEntryHtml: this.options.skipEntryHtml
|
|
45
60
|
});
|
|
46
61
|
console.log(`✓ Generated despia/local.json with ${paths.length} assets`);
|
|
47
62
|
} catch (error) {
|