UNPKG

vite-preload

Version:

Speed up your Vite application by preloading server rendered lazy modules and stylesheets as early as possible

269 lines (265 loc) 9.31 kB
'use strict'; var fs = require('node:fs'); var path = require('node:path'); var utils = require('./utils.cjs'); class ChunkCollector { constructor(manifest, entry, preloadFonts = true, preloadAssets = false, nonce = '', asyncScript = false) { Object.defineProperty(this, "manifest", { enumerable: true, configurable: true, writable: true, value: manifest }); Object.defineProperty(this, "entry", { enumerable: true, configurable: true, writable: true, value: entry }); Object.defineProperty(this, "preloadFonts", { enumerable: true, configurable: true, writable: true, value: preloadFonts }); Object.defineProperty(this, "preloadAssets", { enumerable: true, configurable: true, writable: true, value: preloadAssets }); Object.defineProperty(this, "nonce", { enumerable: true, configurable: true, writable: true, value: nonce }); Object.defineProperty(this, "asyncScript", { enumerable: true, configurable: true, writable: true, value: asyncScript }); Object.defineProperty(this, "modulesIds", { enumerable: true, configurable: true, writable: true, value: new Set() }); Object.defineProperty(this, "preloads", { enumerable: true, configurable: true, writable: true, value: new Map() }); this.__context_collectModuleId = this.__context_collectModuleId.bind(this); this.getChunks = this.getChunks.bind(this); this.getSortedModules = this.getSortedModules.bind(this); this.getTags = this.getTags.bind(this); this.getLinkHeader = this.getLinkHeader.bind(this); this.getLinkHeaders = this.getLinkHeaders.bind(this); // Load the entry modules collectModules('vite/legacy-polyfills', this); collectModules(entry, this); } /** * Function is called by `ChunkCollectorContext` */ __context_collectModuleId(moduleId) { this.modulesIds.add(moduleId); collectModules(moduleId, this); } /** * @deprecated - use getChunks instead */ getSortedModules() { const modules = Array.from(this.preloads.values()); return utils.sortPreloads(modules); } getChunks() { const modules = Array.from(this.preloads.values()); return utils.sortPreloads(modules); } /** * Returns all HTML tags for preload hints and stylesheets. * * See https://vitejs.dev/guide/backend-integration for using your own template */ getTags({ includeEntry, } = {}) { const modules = this.getChunks(); return modules .filter((m) => includeEntry || !m.isEntry) .map(utils.createHtmlTag) .filter((x) => x != null) .join('\n'); } /** * Returns a `Link` header with all chunks to preload, * including entry chunks. * * @example res.setHeader('link', collector.getLinkHeader()); */ getLinkHeader() { const modules = this.getChunks(); return utils.createLinkHeader(modules); } /** * Returns an array of `Link` header values * * @example res.append('link', collector.getLinkHeaders()); */ getLinkHeaders() { return this.getChunks() .map(utils.createSingleLinkHeader) .filter((x) => x != null); } } let manifestFromFile; /** * Create a chunk collector. * This function will throw if not configured correctly */ function createChunkCollector(options) { let manifest = {}; const entry = options.entry || 'index.html'; const enabled = process.env.NODE_ENV === 'production'; if (enabled) { if (typeof options.manifest === 'string') { if (manifestFromFile) { manifest = manifestFromFile; } else { const data = fs.readFileSync(options.manifest, 'utf8'); const json = JSON.parse(data); manifestFromFile = manifest = json; } } else { manifest = options.manifest; } if (!options.manifest) { throw new Error('options.manifest must be provided in production either as a path or object'); } if (!manifest[entry]) { throw new Error(`Vite manifest.json does not contain key "${entry}"`); } if (!manifest[entry].isEntry) { throw new Error(`Module "${entry}" is not an entry module`); } } const collector = new ChunkCollector(manifest, entry, options.preloadFonts, options.preloadAssets, options.nonce, options.asyncScript); return collector; } /* url: '/src/pages/Browse/index.ts', id: '/<absolute>/src/pages/Browse/index.ts', file: '/<absolute>/src/pages/Browse/index.ts', */ /** * https://vitejs.dev/guide/backend-integration * https://github.com/vitejs/vite-plugin-react/tree/main/packages/plugin-react#consistent-components-exports * https://github.com/vitejs/vite-plugin-vue/blob/main/playground/ssr-vue/src/entry-server.js */ /** * This function figures out what modules are used based on the modules rendered by React. * * It follows https://vitejs.dev/guide/backend-integration */ function collectModules(moduleId, { entry, manifest, preloadAssets, preloadFonts, preloads, nonce, asyncScript, }) { // The reported module ID is not in it's own chunk // Possible cause for the missing module in the manifest is build.rollupOptions.output.experimentalMinChunkSize if (!manifest[moduleId] || preloads.has(moduleId)) { return preloads; } const chunks = new Map(); collectChunksRecursively(manifest, moduleId, chunks); for (const chunk of chunks.values()) { if (preloads.has(chunk.file)) { continue; } const isPolyfill = chunk.src === 'vite/legacy-polyfills'; const isPrimaryModule = chunk.src === entry; preloads.set(chunk.file, { // Only the entrypoint module is used as <script module>, everything else is <link rel=modulepreload> rel: isPrimaryModule || isPolyfill ? 'module' : 'modulepreload', href: chunk.file, comment: `chunk: ${chunk.name}, isEntry: ${chunk.isEntry}`, isEntry: chunk.isEntry, nonce, // The polyfill chunk should not be async and it should run before the entry chunk asyncScript: asyncScript && !isPolyfill, }); for (const cssFile of chunk.css || []) { if (preloads.has(cssFile)) continue; preloads.set(cssFile, { rel: 'stylesheet', href: cssFile, comment: `chunk: ${chunk.name}, isEntry: ${chunk.isEntry}`, isEntry: chunk.isEntry, nonce, }); } if (!preloadFonts && !preloadAssets) { continue; } // Assets such as svg, png imports for (const asset of chunk.assets || []) { const ext = path.extname(asset).substring(1); let as; let mimeType; switch (ext) { case 'png': case 'jpg': case 'webp': case 'svg': as = 'image'; mimeType = ext === 'svg' ? 'image/svg+xml' : `image/${ext}`; if (preloadAssets) break; else continue; case 'woff2': case 'woff': case 'ttf': as = 'font'; mimeType = `font/${ext}`; if (preloadFonts) break; else continue; } preloads.set(asset, { rel: 'preload', href: asset, as, type: mimeType, comment: `Asset from chunk ${chunk.name}: ${chunk.file}`, }); } } return preloads; } function collectChunksRecursively(manifest, moduleId, chunks, isEntry) { const chunk = manifest[moduleId]; if (!chunk) { throw new Error(`Missing chunk '${moduleId}'`); } if (chunks.has(moduleId)) { return; } chunks.set(moduleId, { ...chunk, // Any static import in the entry chunk is considered an entry chunk // and inlined by Vite in the generated HTML template but it's not // marked with isEntry: true in the manifest isEntry: isEntry || chunk.isEntry, }); for (const importName of chunk.imports || []) { collectChunksRecursively(manifest, importName, chunks, isEntry || chunk.isEntry); } } exports.ChunkCollector = ChunkCollector; exports.createChunkCollector = createChunkCollector; //# sourceMappingURL=collector.cjs.map