File size: 8,864 Bytes
c592d77
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
"use strict";
Object.defineProperty(exports, "__esModule", {
    value: true
});
Object.defineProperty(exports, /**
 * Rspack Persistent Cache Strategy for Next.js Development
 *
 * Rspack's persistent caching differs from Webpack in how it manages module graphs.
 * While Webpack incrementally updates modules, Rspack operates on complete module
 * graph snapshots for cache restoration.
 *
 * Problem:
 * - Next.js dev server starts with no page modules in the initial entry points
 * - When Rspack restores from persistent cache, it finds no modules and purges
 *   the entire module graph
 * - Later page requests find no cached module information, preventing cache reuse
 *
 * Solution:
 * - Track successfully built page entries after each compilation
 * - Restore these entries on dev server restart to maintain module graph continuity
 * - This ensures previously compiled pages can leverage persistent cache for faster builds
 */ "default", {
    enumerable: true,
    get: function() {
        return HotReloaderRspack;
    }
});
const _path = /*#__PURE__*/ _interop_require_default(require("path"));
const _promises = /*#__PURE__*/ _interop_require_default(require("fs/promises"));
const _crypto = require("crypto");
const _hotreloaderwebpack = /*#__PURE__*/ _interop_require_default(require("./hot-reloader-webpack"));
const _ondemandentryhandler = require("./on-demand-entry-handler");
const _constants = require("../../shared/lib/constants");
function _interop_require_default(obj) {
    return obj && obj.__esModule ? obj : {
        default: obj
    };
}
class HotReloaderRspack extends _hotreloaderwebpack.default {
    async afterCompile(multiCompiler) {
        // Always initialize the fallback error watcher for Rspack.
        // Rspack may restore/retain the previous build's error state, so without this
        // a page that previously failed to build might not be rebuilt on the next request.
        await super.buildFallbackError();
        const rspackStartSpan = this.hotReloaderSpan.traceChild('rspack-after-compile');
        await rspackStartSpan.traceAsyncFn(async ()=>{
            const hash = (0, _crypto.createHash)('sha1');
            multiCompiler.compilers.forEach((compiler)=>{
                const cache = compiler.options.cache;
                if (typeof cache === 'object' && 'version' in cache && cache.version) {
                    hash.update(cache.version);
                    if (compiler.name === _constants.COMPILER_NAMES.client) {
                        this.isClientCacheEnabled = true;
                    } else if (compiler.name === _constants.COMPILER_NAMES.server) {
                        this.isServerCacheEnabled = true;
                    } else if (compiler.name === _constants.COMPILER_NAMES.edgeServer) {
                        this.isEdgeServerCacheEnabled = true;
                    }
                } else {
                    hash.update('-');
                }
                return undefined;
            });
            this.builtEntriesCachePath = _path.default.join(this.distDir, 'cache', 'rspack', hash.digest('hex').substring(0, 16), 'built-entries.json');
            const hasBuiltEntriesCache = await _promises.default.access(this.builtEntriesCachePath).then(()=>true, ()=>false);
            if (hasBuiltEntriesCache) {
                try {
                    const builtEntries = JSON.parse(await _promises.default.readFile(this.builtEntriesCachePath, 'utf-8') || '{}');
                    await Promise.all(Object.keys(builtEntries).map(async (entryKey)=>{
                        const entryData = builtEntries[entryKey];
                        const isEntry = entryData.type === _ondemandentryhandler.EntryTypes.ENTRY;
                        const isChildEntry = entryData.type === _ondemandentryhandler.EntryTypes.CHILD_ENTRY;
                        // Check if the page was removed or disposed and remove it
                        if (isEntry) {
                            const pageExists = !entryData.dispose && await _promises.default.access(entryData.absolutePagePath).then(()=>true, ()=>false);
                            if (!pageExists) {
                                delete builtEntries[entryKey];
                                return;
                            } else if (!('hash' in builtEntries[entryKey]) || builtEntries[entryKey].hash !== await calculateFileHash(entryData.absolutePagePath)) {
                                delete builtEntries[entryKey];
                                return;
                            }
                        }
                        // For child entries, if it has an entry file and it's gone, remove it
                        if (isChildEntry) {
                            if (entryData.absoluteEntryFilePath) {
                                const pageExists = !entryData.dispose && await _promises.default.access(entryData.absoluteEntryFilePath).then(()=>true, ()=>false);
                                if (!pageExists) {
                                    delete builtEntries[entryKey];
                                    return;
                                } else {
                                    if (!('hash' in builtEntries[entryKey]) || builtEntries[entryKey].hash !== await calculateFileHash(entryData.absoluteEntryFilePath)) {
                                        delete builtEntries[entryKey];
                                        return;
                                    }
                                }
                            }
                        }
                    }));
                    Object.assign((0, _ondemandentryhandler.getEntries)(multiCompiler.outputPath), builtEntries);
                } catch (error) {
                    console.error('Rspack failed to read built entries cache: ', error);
                }
            }
        });
    }
    async ensurePage({ page, clientOnly, appPaths, definition, isApp, url }) {
        await super.ensurePage({
            page,
            clientOnly,
            appPaths,
            definition,
            isApp,
            url
        });
        const entries = (0, _ondemandentryhandler.getEntries)(this.multiCompiler.outputPath);
        const builtEntries = {};
        await Promise.all(Object.keys(entries).map(async (entryName)=>{
            const entry = entries[entryName];
            if (entry.status !== _ondemandentryhandler.BUILT) return;
            const result = /^(client|server|edge-server)@(app|pages|root)@(.*)/g.exec(entryName);
            const [, key /* pageType */ , , ] = result// this match should always happen
            ;
            if (key === 'client' && !this.isClientCacheEnabled) return;
            if (key === 'server' && !this.isServerCacheEnabled) return;
            if (key === 'edge-server' && !this.isEdgeServerCacheEnabled) return;
            // TODO: Rspack does not store middleware entries in persistent cache, causing
            // test/integration/middleware-src/test/index.test.ts to fail. This is a temporary
            // workaround to skip middleware entry caching until Rspack properly supports it.
            if (page === '/middleware') {
                return;
            }
            let hash;
            if (entry.type === _ondemandentryhandler.EntryTypes.ENTRY) {
                hash = await calculateFileHash(entry.absolutePagePath);
            } else if (entry.absoluteEntryFilePath) {
                hash = await calculateFileHash(entry.absoluteEntryFilePath);
            }
            if (!hash) {
                return;
            }
            builtEntries[entryName] = entry;
            builtEntries[entryName].hash = hash;
        }));
        const hasBuitEntriesCache = await _promises.default.access(this.builtEntriesCachePath).then(()=>true, ()=>false);
        try {
            if (!hasBuitEntriesCache) {
                await _promises.default.mkdir(_path.default.dirname(this.builtEntriesCachePath), {
                    recursive: true
                });
            }
            await _promises.default.writeFile(this.builtEntriesCachePath, JSON.stringify(builtEntries, null, 2));
        } catch (error) {
            console.error('Rspack failed to write built entries cache: ', error);
        }
    }
    constructor(...args){
        super(...args), this.isClientCacheEnabled = false, this.isServerCacheEnabled = false, this.isEdgeServerCacheEnabled = false;
    }
}
async function calculateFileHash(filePath, algorithm = 'sha256') {
    if (!await _promises.default.access(filePath).then(()=>true, ()=>false)) {
        return;
    }
    const fileBuffer = await _promises.default.readFile(filePath);
    const hash = (0, _crypto.createHash)(algorithm);
    hash.update(fileBuffer);
    return hash.digest('hex');
}

//# sourceMappingURL=hot-reloader-rspack.js.map