"use strict"; /** * Copyright (c) Facebook, Inc. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ Object.defineProperty(exports, "__esModule", { value: true }); exports.build = void 0; const tslib_1 = require("tslib"); const fs_extra_1 = tslib_1.__importDefault(require("fs-extra")); const path_1 = tslib_1.__importDefault(require("path")); const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger")); const utils_1 = require("@docusaurus/utils"); const copy_webpack_plugin_1 = tslib_1.__importDefault(require("copy-webpack-plugin")); const react_loadable_ssr_addon_v5_slorber_1 = tslib_1.__importDefault(require("react-loadable-ssr-addon-v5-slorber")); const webpack_bundle_analyzer_1 = require("webpack-bundle-analyzer"); const webpack_merge_1 = tslib_1.__importDefault(require("webpack-merge")); const server_1 = require("../server"); const brokenLinks_1 = require("../server/brokenLinks"); const client_1 = tslib_1.__importDefault(require("../webpack/client")); const server_2 = tslib_1.__importDefault(require("../webpack/server")); const utils_2 = require("../webpack/utils"); const CleanWebpackPlugin_1 = tslib_1.__importDefault(require("../webpack/plugins/CleanWebpackPlugin")); const i18n_1 = require("../server/i18n"); async function build(siteDirParam = '.', cliOptions = {}, // When running build, we force terminate the process to prevent async // operations from never returning. However, if run as part of docusaurus // deploy, we have to let deploy finish. // See https://github.com/facebook/docusaurus/pull/2496 forceTerminate = true) { process.env.BABEL_ENV = 'production'; process.env.NODE_ENV = 'production'; process.env.DOCUSAURUS_CURRENT_LOCALE = cliOptions.locale; if (cliOptions.dev) { logger_1.default.info `Building in dev mode`; process.env.BABEL_ENV = 'development'; process.env.NODE_ENV = 'development'; } const siteDir = await fs_extra_1.default.realpath(siteDirParam); ['SIGINT', 'SIGTERM'].forEach((sig) => { process.on(sig, () => process.exit()); }); async function tryToBuildLocale({ locale, isLastLocale, }) { try { return await buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLastLocale, }); } catch (err) { throw new Error(logger_1.default.interpolate `Unable to build website for locale name=${locale}.`, { cause: err, }); } } const context = await (0, server_1.loadContext)({ siteDir, outDir: cliOptions.outDir, config: cliOptions.config, locale: cliOptions.locale, localizePath: cliOptions.locale ? false : undefined, }); const i18n = await (0, i18n_1.loadI18n)(context.siteConfig, { locale: cliOptions.locale, }); if (cliOptions.locale) { return tryToBuildLocale({ locale: cliOptions.locale, isLastLocale: true }); } if (i18n.locales.length > 1) { logger_1.default.info `Website will be built for all these locales: ${i18n.locales}`; } // We need the default locale to always be the 1st in the list. If we build it // last, it would "erase" the localized sites built in sub-folders const orderedLocales = [ i18n.defaultLocale, ...i18n.locales.filter((locale) => locale !== i18n.defaultLocale), ]; const results = await (0, utils_1.mapAsyncSequential)(orderedLocales, (locale) => { const isLastLocale = orderedLocales.indexOf(locale) === orderedLocales.length - 1; return tryToBuildLocale({ locale, isLastLocale }); }); return results[0]; } exports.build = build; async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLastLocale, }) { // Temporary workaround to unlock the ability to translate the site config // We'll remove it if a better official API can be designed // See https://github.com/facebook/docusaurus/issues/4542 process.env.DOCUSAURUS_CURRENT_LOCALE = locale; logger_1.default.info `name=${`[${locale}]`} Creating an optimized production build...`; const props = await (0, server_1.load)({ siteDir, outDir: cliOptions.outDir, config: cliOptions.config, locale, localizePath: cliOptions.locale ? false : undefined, }); // Apply user webpack config. const { outDir, generatedFilesDir, plugins, siteConfig: { onBrokenLinks, onBrokenAnchors, staticDirectories: staticDirectoriesOption, }, routes, } = props; const clientManifestPath = path_1.default.join(generatedFilesDir, 'client-manifest.json'); let clientConfig = (0, webpack_merge_1.default)(await (0, client_1.default)(props, cliOptions.minify, true), { plugins: [ // Remove/clean build folders before building bundles. new CleanWebpackPlugin_1.default({ verbose: false }), // Visualize size of webpack output files with an interactive zoomable // tree map. cliOptions.bundleAnalyzer && new webpack_bundle_analyzer_1.BundleAnalyzerPlugin(), // Generate client manifests file that will be used for server bundle. new react_loadable_ssr_addon_v5_slorber_1.default({ filename: clientManifestPath, }), ].filter((x) => Boolean(x)), }); const collectedLinks = {}; const headTags = {}; let serverConfig = await (0, server_2.default)({ props, onLinksCollected: ({ staticPagePath, links, anchors }) => { collectedLinks[staticPagePath] = { links, anchors }; }, onHeadTagsCollected: (staticPagePath, tags) => { headTags[staticPagePath] = tags; }, }); // The staticDirectories option can contain empty directories, or non-existent // directories (e.g. user deleted `static`). Instead of issuing an error, we // just silently filter them out, because user could have never configured it // in the first place (the default option should always "work"). const staticDirectories = (await Promise.all(staticDirectoriesOption.map(async (dir) => { const staticDir = path_1.default.resolve(siteDir, dir); if ((await fs_extra_1.default.pathExists(staticDir)) && (await fs_extra_1.default.readdir(staticDir)).length > 0) { return staticDir; } return ''; }))).filter(Boolean); if (staticDirectories.length > 0) { serverConfig = (0, webpack_merge_1.default)(serverConfig, { plugins: [ new copy_webpack_plugin_1.default({ patterns: staticDirectories.map((dir) => ({ from: dir, to: outDir, toType: 'dir', })), }), ], }); } // Plugin Lifecycle - configureWebpack and configurePostCss. plugins.forEach((plugin) => { const { configureWebpack, configurePostCss } = plugin; if (configurePostCss) { clientConfig = (0, utils_2.applyConfigurePostCss)(configurePostCss.bind(plugin), clientConfig); } if (configureWebpack) { clientConfig = (0, utils_2.applyConfigureWebpack)(configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. clientConfig, false, props.siteConfig.webpack?.jsLoader, plugin.content); serverConfig = (0, utils_2.applyConfigureWebpack)(configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. serverConfig, true, props.siteConfig.webpack?.jsLoader, plugin.content); } }); // Make sure generated client-manifest is cleaned first so we don't reuse // the one from previous builds. if (await fs_extra_1.default.pathExists(clientManifestPath)) { await fs_extra_1.default.unlink(clientManifestPath); } // Run webpack to build JS bundle (client) and static html files (server). await (0, utils_2.compile)([clientConfig, serverConfig]); // Remove server.bundle.js because it is not needed. if (typeof serverConfig.output?.filename === 'string') { const serverBundle = path_1.default.join(outDir, serverConfig.output.filename); if (await fs_extra_1.default.pathExists(serverBundle)) { await fs_extra_1.default.unlink(serverBundle); } } // Plugin Lifecycle - postBuild. await Promise.all(plugins.map(async (plugin) => { if (!plugin.postBuild) { return; } await plugin.postBuild({ ...props, head: headTags, content: plugin.content, }); })); await (0, brokenLinks_1.handleBrokenLinks)({ collectedLinks, routes, onBrokenLinks, onBrokenAnchors, }); logger_1.default.success `Generated static files in path=${path_1.default.relative(process.cwd(), outDir)}.`; if (isLastLocale) { logger_1.default.info `Use code=${'npm run serve'} command to test your build locally.`; } if (forceTerminate && isLastLocale && !cliOptions.bundleAnalyzer) { process.exit(0); } return outDir; }