UNPKG

@prerenderer/webpack-plugin

Version:

Flexible, framework-agnostic static site generation for apps built with webpack.

177 lines (176 loc) 10.1 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const schema_utils_1 = require("schema-utils"); const Options_1 = require("./Options"); const webpack_1 = require("webpack"); const ts_deepmerge_1 = __importDefault(require("ts-deepmerge")); const path_1 = __importDefault(require("path")); const html_webpack_plugin_1 = __importDefault(require("html-webpack-plugin")); class WebpackPrerenderSPAPlugin { constructor(options = {}) { (0, schema_utils_1.validate)(Options_1.schema, options, { name: 'Prerender SPA Plugin', baseDataPath: 'options', }); this.options = ts_deepmerge_1.default.withOptions({ mergeArrays: false }, Options_1.defaultOptions, options); } prerender(compiler_1, compilation_1) { return __awaiter(this, arguments, void 0, function* (compiler, compilation, alreadyRenderedRoutes = []) { const indexPath = this.options.indexPath; const entryPath = this.options.entryPath || indexPath; if (!(entryPath in compilation.assets)) { compilation.warnings.push(new webpack_1.WebpackError('[prerender-spa-plugin] Could not find the entryPath! Doing nothing.')); return false; } const { default: Prerenderer } = yield Promise.resolve().then(() => __importStar(require('@prerenderer/prerenderer'))); const PrerendererInstance = new Prerenderer(Object.assign({ staticDir: compiler.options.output.path || '/' }, this.options)); // Modify the express server to serve the files from the webpack compiler because they are not written on disk yet PrerendererInstance.hookServer((server) => { const express = server.getExpressServer(); // Express doesn't have complete typings yet // eslint-disable-next-line @typescript-eslint/no-unsafe-member-access const routes = express._router.stack; routes.forEach((route, i) => { if (route.route && route.route.path === '*') { routes.splice(i, 1); } }, 'post-fallback'); express.get('*', (req, res) => { let url = req.path.slice(0, req.path.endsWith('/') ? -1 : undefined); const publicPath = typeof compilation.outputOptions.publicPath === 'string' && compilation.outputOptions.publicPath !== 'auto' ? compilation.outputOptions.publicPath || '/' : '/'; if (url.startsWith(publicPath)) { url = url.slice(publicPath.length); } url = url in compilation.assets || url.includes('.') ? url : url + '/' + indexPath; if (url.startsWith('/')) { url = url.slice(1); } if (this.options.urlModifier) { url = this.options.urlModifier(url); } if (url in compilation.assets) { if (url.endsWith('.json')) { const source = compilation.assets[url].source(); res.json(JSON.parse(typeof source === 'string' ? source : source.toString())); } else { try { res.type(path_1.default.extname(url)); res.send(compilation.assets[url].source()); } catch (e) { res.status(500); compilation.errors.push(new webpack_1.WebpackError('[prerender-spa-plugin] Failed to deliver ' + url + ', is the type of the file correct?')); } } } else if (entryPath in compilation.assets) { res.send(compilation.assets[entryPath].source()); } else { compilation.errors.push(new webpack_1.WebpackError('[prerender-spa-plugin] ' + url + ' not found during prerender')); res.status(404); } }); }); try { const routes = [...new Set(this.options.routes || [])]; if (routes.length) { yield PrerendererInstance.initialize(); const renderedRoutes = yield PrerendererInstance.renderRoutes(routes); alreadyRenderedRoutes.concat(routes); // Calculate outputPath if it hasn't been set already. renderedRoutes.forEach(processedRoute => { // Create dirs and write prerendered files. if (!processedRoute.outputPath) { processedRoute.outputPath = path_1.default.join(processedRoute.route, indexPath); if (processedRoute.outputPath.startsWith('/') || processedRoute.outputPath.startsWith('\\')) { processedRoute.outputPath = processedRoute.outputPath.slice(1); } } if (processedRoute.outputPath === entryPath && this.options.fallback) { const fallback = typeof this.options.fallback === 'string' ? this.options.fallback : '_fallback'; const ext = path_1.default.extname(processedRoute.outputPath); const fileName = processedRoute.outputPath.slice(0, -ext.length) + fallback + ext; if (!(fileName in compilation.assets)) { compilation.emitAsset(fileName, compilation.assets[processedRoute.outputPath]); } } // false positive as calling call(compilation) right after // eslint-disable-next-line @typescript-eslint/unbound-method const fn = processedRoute.outputPath in compilation.assets ? compilation.updateAsset : compilation.emitAsset; fn.call(compilation, processedRoute.outputPath, new compiler.webpack.sources.RawSource(processedRoute.html.trim(), false), { prerendered: true, }); }); } } catch (err) { const msg = '[prerender-spa-plugin] Unable to prerender all routes!'; compilation.errors.push(new webpack_1.WebpackError(msg)); if (err instanceof Error) { compilation.errors.push(new webpack_1.WebpackError(err.message)); } else if (typeof err === 'object' && err) { compilation.errors.push(new webpack_1.WebpackError(JSON.stringify(err))); } } yield PrerendererInstance.destroy(); }); } apply(compiler) { const pluginName = this.constructor.name; compiler.hooks.compilation.tap(pluginName, (compilation) => { // Check if HtmlWebpackPlugin was used in the compilation const isHtmlWebpackPluginUsed = compiler.options.plugins.some((plugin) => plugin instanceof html_webpack_plugin_1.default); if (!isHtmlWebpackPluginUsed) { compilation.warnings.push(new webpack_1.WebpackError('[prerender-spa-plugin] The HtmlWebpackPlugin is missing from webpack, the prerender plugin will do nothing.')); } else { const hooks = html_webpack_plugin_1.default.getHooks(compilation); hooks.afterEmit.tapPromise(pluginName, (out) => __awaiter(this, void 0, void 0, function* () { yield this.prerender(compiler, compilation); return out; })); } }); } } exports.default = WebpackPrerenderSPAPlugin;