@docusaurus/core 3.5.2 → 3.6.0
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/bin/beforeCli.mjs +1 -1
- package/bin/docusaurus.mjs +15 -4
- package/lib/babel/preset.d.ts +2 -2
- package/lib/babel/preset.js +10 -71
- package/lib/client/renderToHtml.js +15 -51
- package/lib/client/serverEntry.d.ts +1 -1
- package/lib/client/serverEntry.js +2 -0
- package/lib/commands/{build.d.ts → build/build.d.ts} +4 -3
- package/lib/commands/build/build.js +94 -0
- package/lib/commands/build/buildLocale.d.ts +13 -0
- package/lib/commands/build/buildLocale.js +143 -0
- package/lib/commands/deploy.d.ts +2 -1
- package/lib/commands/deploy.js +2 -2
- package/lib/commands/serve.js +2 -2
- package/lib/commands/start/utils.js +5 -6
- package/lib/commands/start/webpack.js +15 -9
- package/lib/commands/writeHeadingIds.js +1 -2
- package/lib/commands/writeTranslations.js +6 -6
- package/lib/index.d.ts +1 -1
- package/lib/index.js +1 -1
- package/lib/server/configValidation.d.ts +3 -1
- package/lib/server/configValidation.js +35 -4
- package/lib/server/i18n.d.ts +1 -1
- package/lib/server/i18n.js +1 -1
- package/lib/server/plugins/plugins.js +13 -13
- package/lib/server/plugins/synthetic.d.ts +1 -1
- package/lib/server/plugins/synthetic.js +14 -17
- package/lib/server/site.js +10 -4
- package/lib/server/translations/translationsExtractor.d.ts +5 -11
- package/lib/server/translations/translationsExtractor.js +8 -196
- package/lib/ssg/ssg.d.ts +21 -0
- package/lib/{ssg.js → ssg/ssg.js} +91 -82
- package/lib/ssg/ssgExecutor.d.ts +16 -0
- package/lib/ssg/ssgExecutor.js +34 -0
- package/lib/{server/utils.d.ts → ssg/ssgNodeRequire.d.ts} +5 -2
- package/lib/ssg/ssgNodeRequire.js +40 -0
- package/lib/ssg/ssgParams.d.ts +28 -0
- package/lib/ssg/ssgParams.js +36 -0
- package/lib/{templates/templates.d.ts → ssg/ssgTemplate.d.ts} +7 -6
- package/lib/{templates/templates.js → ssg/ssgTemplate.js} +11 -9
- package/lib/ssg/ssgUtils.d.ts +17 -0
- package/lib/ssg/ssgUtils.js +58 -0
- package/lib/webpack/base.d.ts +4 -2
- package/lib/webpack/base.js +33 -20
- package/lib/webpack/client.d.ts +7 -3
- package/lib/webpack/client.js +32 -14
- package/lib/webpack/configure.d.ts +20 -6
- package/lib/webpack/configure.js +31 -16
- package/lib/webpack/plugins/ChunkAssetPlugin.d.ts +0 -11
- package/lib/webpack/plugins/ChunkAssetPlugin.js +48 -33
- package/lib/webpack/plugins/ForceTerminatePlugin.js +2 -2
- package/lib/webpack/plugins/StaticDirectoriesCopyPlugin.d.ts +2 -2
- package/lib/webpack/plugins/StaticDirectoriesCopyPlugin.js +5 -2
- package/lib/webpack/server.d.ts +3 -2
- package/lib/webpack/server.js +12 -10
- package/lib/webpack/{minification.d.ts → utils/getHttpsConfig.d.ts} +4 -2
- package/lib/webpack/utils/getHttpsConfig.js +60 -0
- package/package.json +19 -44
- package/lib/commands/build.js +0 -240
- package/lib/server/utils.js +0 -20
- package/lib/ssg.d.ts +0 -35
- package/lib/utils.d.ts +0 -9
- package/lib/utils.js +0 -78
- package/lib/webpack/minification.js +0 -96
- package/lib/webpack/plugins/WaitPlugin.d.ts +0 -16
- package/lib/webpack/plugins/WaitPlugin.js +0 -47
- package/lib/webpack/utils.d.ts +0 -33
- package/lib/webpack/utils.js +0 -215
- /package/lib/{templates/ssr.html.template.d.ts → ssg/ssgTemplate.html.d.ts} +0 -0
- /package/lib/{templates/ssr.html.template.js → ssg/ssgTemplate.html.js} +0 -0
- /package/lib/{templates → webpack/templates}/dev.html.template.ejs +0 -0
|
@@ -6,33 +6,12 @@
|
|
|
6
6
|
* LICENSE file in the root directory of this source tree.
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
-
exports.globSourceCodeFilePaths = globSourceCodeFilePaths;
|
|
10
9
|
exports.extractSiteSourceCodeTranslations = extractSiteSourceCodeTranslations;
|
|
11
|
-
exports.extractAllSourceCodeFileTranslations = extractAllSourceCodeFileTranslations;
|
|
12
|
-
exports.extractSourceCodeFileTranslations = extractSourceCodeFileTranslations;
|
|
13
10
|
const tslib_1 = require("tslib");
|
|
14
11
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
15
|
-
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
16
12
|
const logger_1 = tslib_1.__importDefault(require("@docusaurus/logger"));
|
|
17
|
-
const traverse_1 = tslib_1.__importDefault(require("@babel/traverse"));
|
|
18
|
-
const generator_1 = tslib_1.__importDefault(require("@babel/generator"));
|
|
19
|
-
const core_1 = require("@babel/core");
|
|
20
13
|
const utils_1 = require("@docusaurus/utils");
|
|
21
|
-
const
|
|
22
|
-
// We only support extracting source code translations from these kind of files
|
|
23
|
-
const TranslatableSourceCodeExtension = new Set([
|
|
24
|
-
'.js',
|
|
25
|
-
'.jsx',
|
|
26
|
-
'.ts',
|
|
27
|
-
'.tsx',
|
|
28
|
-
// TODO support md/mdx too? (may be overkill)
|
|
29
|
-
// need to compile the MDX to JSX first and remove front matter
|
|
30
|
-
// '.md',
|
|
31
|
-
// '.mdx',
|
|
32
|
-
]);
|
|
33
|
-
function isTranslatableSourceCodePath(filePath) {
|
|
34
|
-
return TranslatableSourceCodeExtension.has(path_1.default.extname(filePath));
|
|
35
|
-
}
|
|
14
|
+
const babel_1 = require("@docusaurus/babel");
|
|
36
15
|
function getSiteSourceCodeFilePaths(siteDir) {
|
|
37
16
|
return [path_1.default.join(siteDir, utils_1.SRC_DIR_NAME)];
|
|
38
17
|
}
|
|
@@ -49,10 +28,6 @@ function getPluginSourceCodeFilePaths(plugin) {
|
|
|
49
28
|
}
|
|
50
29
|
return codePaths.map((p) => path_1.default.resolve(plugin.path, p));
|
|
51
30
|
}
|
|
52
|
-
async function globSourceCodeFilePaths(dirPaths) {
|
|
53
|
-
const filePaths = await (0, utils_2.safeGlobby)(dirPaths);
|
|
54
|
-
return filePaths.filter(isTranslatableSourceCodePath);
|
|
55
|
-
}
|
|
56
31
|
async function getSourceCodeFilePaths(siteDir, plugins) {
|
|
57
32
|
const sitePaths = getSiteSourceCodeFilePaths(siteDir);
|
|
58
33
|
// The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths
|
|
@@ -61,9 +36,13 @@ async function getSourceCodeFilePaths(siteDir, plugins) {
|
|
|
61
36
|
// new lifecycle method for that???
|
|
62
37
|
const pluginsPaths = plugins.flatMap(getPluginSourceCodeFilePaths);
|
|
63
38
|
const allPaths = [...sitePaths, ...pluginsPaths];
|
|
64
|
-
return
|
|
39
|
+
return (0, utils_1.globTranslatableSourceFiles)(allPaths);
|
|
65
40
|
}
|
|
66
|
-
async function extractSiteSourceCodeTranslations(siteDir, plugins,
|
|
41
|
+
async function extractSiteSourceCodeTranslations({ siteDir, plugins, extraSourceCodeFilePaths = [], }) {
|
|
42
|
+
const babelOptions = (0, babel_1.getBabelOptions)({
|
|
43
|
+
isServer: true,
|
|
44
|
+
babelOptions: await (0, babel_1.getCustomBabelConfigFilePath)(siteDir),
|
|
45
|
+
});
|
|
67
46
|
// Should we warn here if the same translation "key" is found in multiple
|
|
68
47
|
// source code files?
|
|
69
48
|
function toTranslationFileContent(sourceCodeFileTranslations) {
|
|
@@ -74,7 +53,7 @@ async function extractSiteSourceCodeTranslations(siteDir, plugins, babelOptions,
|
|
|
74
53
|
...sourceCodeFilePaths,
|
|
75
54
|
...extraSourceCodeFilePaths,
|
|
76
55
|
];
|
|
77
|
-
const sourceCodeFilesTranslations = await extractAllSourceCodeFileTranslations(allSourceCodeFilePaths, babelOptions);
|
|
56
|
+
const sourceCodeFilesTranslations = await (0, babel_1.extractAllSourceCodeFileTranslations)(allSourceCodeFilePaths, babelOptions);
|
|
78
57
|
logSourceCodeFileTranslationsWarnings(sourceCodeFilesTranslations);
|
|
79
58
|
return toTranslationFileContent(sourceCodeFilesTranslations);
|
|
80
59
|
}
|
|
@@ -85,170 +64,3 @@ function logSourceCodeFileTranslationsWarnings(sourceCodeFilesTranslations) {
|
|
|
85
64
|
}
|
|
86
65
|
});
|
|
87
66
|
}
|
|
88
|
-
async function extractAllSourceCodeFileTranslations(sourceCodeFilePaths, babelOptions) {
|
|
89
|
-
return Promise.all(sourceCodeFilePaths.flatMap((sourceFilePath) => extractSourceCodeFileTranslations(sourceFilePath, babelOptions)));
|
|
90
|
-
}
|
|
91
|
-
async function extractSourceCodeFileTranslations(sourceCodeFilePath, babelOptions) {
|
|
92
|
-
try {
|
|
93
|
-
const code = await fs_extra_1.default.readFile(sourceCodeFilePath, 'utf8');
|
|
94
|
-
const ast = (0, core_1.parse)(code, {
|
|
95
|
-
...babelOptions,
|
|
96
|
-
ast: true,
|
|
97
|
-
// filename is important, because babel does not process the same files
|
|
98
|
-
// according to their js/ts extensions.
|
|
99
|
-
// See https://twitter.com/NicoloRibaudo/status/1321130735605002243
|
|
100
|
-
filename: sourceCodeFilePath,
|
|
101
|
-
});
|
|
102
|
-
const translations = extractSourceCodeAstTranslations(ast, sourceCodeFilePath);
|
|
103
|
-
return translations;
|
|
104
|
-
}
|
|
105
|
-
catch (err) {
|
|
106
|
-
logger_1.default.error `Error while attempting to extract Docusaurus translations from source code file at path=${sourceCodeFilePath}.`;
|
|
107
|
-
throw err;
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
/*
|
|
111
|
-
Need help understanding this?
|
|
112
|
-
|
|
113
|
-
Useful resources:
|
|
114
|
-
https://github.com/jamiebuilds/babel-handbook/blob/master/translations/en/plugin-handbook.md
|
|
115
|
-
https://github.com/formatjs/formatjs/blob/main/packages/babel-plugin-formatjs/index.ts
|
|
116
|
-
https://github.com/pugjs/babel-walk
|
|
117
|
-
*/
|
|
118
|
-
function extractSourceCodeAstTranslations(ast, sourceCodeFilePath) {
|
|
119
|
-
function sourceWarningPart(node) {
|
|
120
|
-
return `File: ${sourceCodeFilePath} at line ${node.loc?.start.line ?? '?'}
|
|
121
|
-
Full code: ${(0, generator_1.default)(node).code}`;
|
|
122
|
-
}
|
|
123
|
-
const translations = {};
|
|
124
|
-
const warnings = [];
|
|
125
|
-
let translateComponentName;
|
|
126
|
-
let translateFunctionName;
|
|
127
|
-
// First pass: find import declarations of Translate / translate.
|
|
128
|
-
// If not found, don't process the rest to avoid false positives
|
|
129
|
-
(0, traverse_1.default)(ast, {
|
|
130
|
-
ImportDeclaration(path) {
|
|
131
|
-
if (path.node.importKind === 'type' ||
|
|
132
|
-
path.get('source').node.value !== '@docusaurus/Translate') {
|
|
133
|
-
return;
|
|
134
|
-
}
|
|
135
|
-
const importSpecifiers = path.get('specifiers');
|
|
136
|
-
const defaultImport = importSpecifiers.find((specifier) => specifier.node.type === 'ImportDefaultSpecifier');
|
|
137
|
-
const callbackImport = importSpecifiers.find((specifier) => specifier.node.type === 'ImportSpecifier' &&
|
|
138
|
-
(specifier.get('imported')
|
|
139
|
-
.node.name === 'translate' ||
|
|
140
|
-
specifier.get('imported')
|
|
141
|
-
.node.value === 'translate'));
|
|
142
|
-
translateComponentName = defaultImport?.get('local').node.name;
|
|
143
|
-
translateFunctionName = callbackImport?.get('local').node.name;
|
|
144
|
-
},
|
|
145
|
-
});
|
|
146
|
-
(0, traverse_1.default)(ast, {
|
|
147
|
-
...(translateComponentName && {
|
|
148
|
-
JSXElement(path) {
|
|
149
|
-
if (!path
|
|
150
|
-
.get('openingElement')
|
|
151
|
-
.get('name')
|
|
152
|
-
.isJSXIdentifier({ name: translateComponentName })) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
function evaluateJSXProp(propName) {
|
|
156
|
-
const attributePath = path
|
|
157
|
-
.get('openingElement.attributes')
|
|
158
|
-
.find((attr) => attr.isJSXAttribute() &&
|
|
159
|
-
attr.get('name').isJSXIdentifier({ name: propName }));
|
|
160
|
-
if (attributePath) {
|
|
161
|
-
const attributeValue = attributePath.get('value');
|
|
162
|
-
const attributeValueEvaluated = attributeValue.isJSXExpressionContainer()
|
|
163
|
-
? attributeValue.get('expression').evaluate()
|
|
164
|
-
: attributeValue.evaluate();
|
|
165
|
-
if (attributeValueEvaluated.confident &&
|
|
166
|
-
typeof attributeValueEvaluated.value === 'string') {
|
|
167
|
-
return attributeValueEvaluated.value;
|
|
168
|
-
}
|
|
169
|
-
warnings.push(`<Translate> prop=${propName} should be a statically evaluable object.
|
|
170
|
-
Example: <Translate id="optional id" description="optional description">Message</Translate>
|
|
171
|
-
Dynamically constructed values are not allowed, because they prevent translations to be extracted.
|
|
172
|
-
${sourceWarningPart(path.node)}`);
|
|
173
|
-
}
|
|
174
|
-
return undefined;
|
|
175
|
-
}
|
|
176
|
-
const id = evaluateJSXProp('id');
|
|
177
|
-
const description = evaluateJSXProp('description');
|
|
178
|
-
let message;
|
|
179
|
-
const childrenPath = path.get('children');
|
|
180
|
-
// Handle empty content
|
|
181
|
-
if (!childrenPath.length) {
|
|
182
|
-
if (!id) {
|
|
183
|
-
warnings.push(`<Translate> without children must have id prop.
|
|
184
|
-
Example: <Translate id="my-id" />
|
|
185
|
-
${sourceWarningPart(path.node)}`);
|
|
186
|
-
}
|
|
187
|
-
else {
|
|
188
|
-
translations[id] = {
|
|
189
|
-
message: id,
|
|
190
|
-
...(description && { description }),
|
|
191
|
-
};
|
|
192
|
-
}
|
|
193
|
-
return;
|
|
194
|
-
}
|
|
195
|
-
// Handle single non-empty content
|
|
196
|
-
const singleChildren = childrenPath
|
|
197
|
-
// Remove empty/useless text nodes that might be around our
|
|
198
|
-
// translation! Makes the translation system more reliable to JSX
|
|
199
|
-
// formatting issues
|
|
200
|
-
.filter((children) => !(children.isJSXText() &&
|
|
201
|
-
children.node.value.replace('\n', '').trim() === ''))
|
|
202
|
-
.pop();
|
|
203
|
-
const isJSXText = singleChildren?.isJSXText();
|
|
204
|
-
const isJSXExpressionContainer = singleChildren?.isJSXExpressionContainer() &&
|
|
205
|
-
singleChildren.get('expression').evaluate().confident;
|
|
206
|
-
if (isJSXText || isJSXExpressionContainer) {
|
|
207
|
-
message = isJSXText
|
|
208
|
-
? singleChildren.node.value.trim().replace(/\s+/g, ' ')
|
|
209
|
-
: String(singleChildren.get('expression').evaluate().value);
|
|
210
|
-
translations[id ?? message] = {
|
|
211
|
-
message,
|
|
212
|
-
...(description && { description }),
|
|
213
|
-
};
|
|
214
|
-
}
|
|
215
|
-
else {
|
|
216
|
-
warnings.push(`Translate content could not be extracted. It has to be a static string and use optional but static props, like <Translate id="my-id" description="my-description">text</Translate>.
|
|
217
|
-
${sourceWarningPart(path.node)}`);
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
}),
|
|
221
|
-
...(translateFunctionName && {
|
|
222
|
-
CallExpression(path) {
|
|
223
|
-
if (!path.get('callee').isIdentifier({ name: translateFunctionName })) {
|
|
224
|
-
return;
|
|
225
|
-
}
|
|
226
|
-
const args = path.get('arguments');
|
|
227
|
-
if (args.length === 1 || args.length === 2) {
|
|
228
|
-
const firstArgPath = args[0];
|
|
229
|
-
// translate("x" + "y"); => translate("xy");
|
|
230
|
-
const firstArgEvaluated = firstArgPath.evaluate();
|
|
231
|
-
if (firstArgEvaluated.confident &&
|
|
232
|
-
typeof firstArgEvaluated.value === 'object') {
|
|
233
|
-
const { message, id, description } = firstArgEvaluated.value;
|
|
234
|
-
translations[String(id ?? message)] = {
|
|
235
|
-
message: String(message ?? id),
|
|
236
|
-
...(Boolean(description) && { description: String(description) }),
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
else {
|
|
240
|
-
warnings.push(`translate() first arg should be a statically evaluable object.
|
|
241
|
-
Example: translate({message: "text",id: "optional.id",description: "optional description"}
|
|
242
|
-
Dynamically constructed values are not allowed, because they prevent translations to be extracted.
|
|
243
|
-
${sourceWarningPart(path.node)}`);
|
|
244
|
-
}
|
|
245
|
-
}
|
|
246
|
-
else {
|
|
247
|
-
warnings.push(`translate() function only takes 1 or 2 args
|
|
248
|
-
${sourceWarningPart(path.node)}`);
|
|
249
|
-
}
|
|
250
|
-
},
|
|
251
|
-
}),
|
|
252
|
-
});
|
|
253
|
-
return { sourceCodeFilePath, translations, warnings };
|
|
254
|
-
}
|
package/lib/ssg/ssg.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { SSGParams } from './ssgParams';
|
|
8
|
+
import type { AppRenderer, SiteCollectedData } from '../common';
|
|
9
|
+
export declare function loadAppRenderer({ serverBundlePath, }: {
|
|
10
|
+
serverBundlePath: string;
|
|
11
|
+
}): Promise<AppRenderer>;
|
|
12
|
+
export declare function printSSGWarnings(results: {
|
|
13
|
+
pathname: string;
|
|
14
|
+
warnings: string[];
|
|
15
|
+
}[]): void;
|
|
16
|
+
export declare function generateStaticFiles({ pathnames, params, }: {
|
|
17
|
+
pathnames: string[];
|
|
18
|
+
params: SSGParams;
|
|
19
|
+
}): Promise<{
|
|
20
|
+
collectedData: SiteCollectedData;
|
|
21
|
+
}>;
|
|
@@ -7,31 +7,26 @@
|
|
|
7
7
|
*/
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
exports.loadAppRenderer = loadAppRenderer;
|
|
10
|
+
exports.printSSGWarnings = printSSGWarnings;
|
|
10
11
|
exports.generateStaticFiles = generateStaticFiles;
|
|
11
|
-
exports.generateHashRouterEntrypoint = generateHashRouterEntrypoint;
|
|
12
12
|
const tslib_1 = require("tslib");
|
|
13
13
|
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
14
|
-
const module_1 = require("module");
|
|
15
14
|
const path_1 = tslib_1.__importDefault(require("path"));
|
|
16
15
|
const lodash_1 = tslib_1.__importDefault(require("lodash"));
|
|
16
|
+
// TODO eval is archived / unmaintained: https://github.com/pierrec/node-eval
|
|
17
|
+
// We should internalize/modernize it
|
|
17
18
|
const eval_1 = tslib_1.__importDefault(require("eval"));
|
|
18
19
|
const p_map_1 = tslib_1.__importDefault(require("p-map"));
|
|
19
|
-
const
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
const
|
|
23
|
-
|
|
24
|
-
// Waiting for feedback before documenting this officially?
|
|
25
|
-
const Concurrency = process.env.DOCUSAURUS_SSR_CONCURRENCY
|
|
26
|
-
? parseInt(process.env.DOCUSAURUS_SSR_CONCURRENCY, 10)
|
|
27
|
-
: // Not easy to define a reasonable option default
|
|
28
|
-
// Will still be better than Infinity
|
|
29
|
-
// See also https://github.com/sindresorhus/p-map/issues/24
|
|
30
|
-
32;
|
|
20
|
+
const logger_1 = tslib_1.__importStar(require("@docusaurus/logger"));
|
|
21
|
+
const bundler_1 = require("@docusaurus/bundler");
|
|
22
|
+
const ssgTemplate_1 = require("./ssgTemplate");
|
|
23
|
+
const ssgUtils_1 = require("./ssgUtils");
|
|
24
|
+
const ssgNodeRequire_1 = require("./ssgNodeRequire");
|
|
31
25
|
async function loadAppRenderer({ serverBundlePath, }) {
|
|
32
|
-
const source = await
|
|
33
|
-
|
|
26
|
+
const source = await logger_1.PerfLogger.async(`Load server bundle`, () => fs_extra_1.default.readFile(serverBundlePath));
|
|
27
|
+
logger_1.PerfLogger.log(`Server bundle size = ${(source.length / 1024000).toFixed(3)} MB`);
|
|
34
28
|
const filename = path_1.default.basename(serverBundlePath);
|
|
29
|
+
const ssgRequire = (0, ssgNodeRequire_1.createSSGRequire)(serverBundlePath);
|
|
35
30
|
const globals = {
|
|
36
31
|
// When using "new URL('file.js', import.meta.url)", Webpack will emit
|
|
37
32
|
// __filename, and this plugin will throw. not sure the __filename value
|
|
@@ -40,43 +35,91 @@ async function loadAppRenderer({ serverBundlePath, }) {
|
|
|
40
35
|
__filename: '',
|
|
41
36
|
// This uses module.createRequire() instead of very old "require-like" lib
|
|
42
37
|
// See also: https://github.com/pierrec/node-eval/issues/33
|
|
43
|
-
require:
|
|
38
|
+
require: ssgRequire.require,
|
|
44
39
|
};
|
|
45
|
-
const serverEntry = await
|
|
40
|
+
const serverEntry = await logger_1.PerfLogger.async(`Evaluate server bundle`, () => (0, eval_1.default)(source,
|
|
46
41
|
/* filename: */ filename,
|
|
47
42
|
/* scope: */ globals,
|
|
48
43
|
/* includeGlobals: */ true));
|
|
49
44
|
if (!serverEntry?.default || typeof serverEntry.default !== 'function') {
|
|
50
45
|
throw new Error(`Server bundle export from "${filename}" must be a function that renders the Docusaurus React app.`);
|
|
51
46
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
function pathnameToFilename({ pathname, trailingSlash, }) {
|
|
55
|
-
const outputFileName = pathname.replace(/^[/\\]/, ''); // Remove leading slashes for webpack-dev-server
|
|
56
|
-
// Paths ending with .html are left untouched
|
|
57
|
-
if (/\.html?$/i.test(outputFileName)) {
|
|
58
|
-
return outputFileName;
|
|
47
|
+
async function shutdown() {
|
|
48
|
+
ssgRequire.cleanup();
|
|
59
49
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
50
|
+
return {
|
|
51
|
+
render: serverEntry.default,
|
|
52
|
+
shutdown,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
function printSSGWarnings(results) {
|
|
56
|
+
// Escape hatch because SWC is quite aggressive to report errors
|
|
57
|
+
// See https://github.com/facebook/docusaurus/pull/10554
|
|
58
|
+
// See https://github.com/swc-project/swc/discussions/9616#discussioncomment-10846201
|
|
59
|
+
if (process.env.DOCUSAURUS_IGNORE_SSG_WARNINGS === 'true') {
|
|
60
|
+
return;
|
|
63
61
|
}
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
62
|
+
const ignoredWarnings = [
|
|
63
|
+
// TODO React/Docusaurus emit NULL chars, and minifier detects it
|
|
64
|
+
// see https://github.com/facebook/docusaurus/issues/9985
|
|
65
|
+
'Unexpected null character',
|
|
66
|
+
];
|
|
67
|
+
const keepWarning = (warning) => {
|
|
68
|
+
return !ignoredWarnings.some((iw) => warning.includes(iw));
|
|
69
|
+
};
|
|
70
|
+
const resultsWithWarnings = results
|
|
71
|
+
.map((result) => {
|
|
72
|
+
return {
|
|
73
|
+
...result,
|
|
74
|
+
warnings: result.warnings.filter(keepWarning),
|
|
75
|
+
};
|
|
76
|
+
})
|
|
77
|
+
.filter((result) => result.warnings.length > 0);
|
|
78
|
+
if (resultsWithWarnings.length) {
|
|
79
|
+
const message = `Docusaurus static site generation process emitted warnings for ${resultsWithWarnings.length} path${resultsWithWarnings.length ? 's' : ''}
|
|
80
|
+
This is non-critical and can be disabled with DOCUSAURUS_IGNORE_SSG_WARNINGS=true
|
|
81
|
+
Troubleshooting guide: https://github.com/facebook/docusaurus/discussions/10580
|
|
82
|
+
|
|
83
|
+
- ${resultsWithWarnings
|
|
84
|
+
.map((result) => `${logger_1.default.path(result.pathname)}:
|
|
85
|
+
- ${result.warnings.join('\n - ')}
|
|
86
|
+
`)
|
|
87
|
+
.join('\n- ')}`;
|
|
88
|
+
logger_1.default.warn(message);
|
|
68
89
|
}
|
|
69
|
-
return `${outputFileName}.html`;
|
|
70
90
|
}
|
|
71
|
-
async function generateStaticFiles({ pathnames,
|
|
91
|
+
async function generateStaticFiles({ pathnames, params, }) {
|
|
92
|
+
const [renderer, htmlMinifier, ssgTemplate] = await Promise.all([
|
|
93
|
+
logger_1.PerfLogger.async('Load App renderer', () => loadAppRenderer({
|
|
94
|
+
serverBundlePath: params.serverBundlePath,
|
|
95
|
+
})),
|
|
96
|
+
logger_1.PerfLogger.async('Load HTML minifier', () => (0, bundler_1.getHtmlMinifier)({
|
|
97
|
+
type: params.htmlMinifierType,
|
|
98
|
+
})),
|
|
99
|
+
logger_1.PerfLogger.async('Compile SSG template', () => (0, ssgTemplate_1.compileSSGTemplate)(params.ssgTemplateContent)),
|
|
100
|
+
]);
|
|
72
101
|
// Note that we catch all async errors on purpose
|
|
73
102
|
// Docusaurus presents all the SSG errors to the user, not just the first one
|
|
74
103
|
const results = await (0, p_map_1.default)(pathnames, async (pathname) => generateStaticFile({
|
|
75
104
|
pathname,
|
|
76
105
|
renderer,
|
|
77
106
|
params,
|
|
78
|
-
|
|
79
|
-
|
|
107
|
+
htmlMinifier,
|
|
108
|
+
ssgTemplate,
|
|
109
|
+
}).then((result) => ({
|
|
110
|
+
pathname,
|
|
111
|
+
result,
|
|
112
|
+
error: null,
|
|
113
|
+
warnings: result.warnings,
|
|
114
|
+
}), (error) => ({
|
|
115
|
+
pathname,
|
|
116
|
+
result: null,
|
|
117
|
+
error: error,
|
|
118
|
+
warnings: [],
|
|
119
|
+
})), { concurrency: ssgUtils_1.SSGConcurrency });
|
|
120
|
+
await renderer.shutdown();
|
|
121
|
+
printSSGWarnings(results);
|
|
122
|
+
const [allSSGErrors, allSSGSuccesses] = lodash_1.default.partition(results, (result) => !!result.error);
|
|
80
123
|
if (allSSGErrors.length > 0) {
|
|
81
124
|
const message = `Docusaurus static site generation failed for ${allSSGErrors.length} path${allSSGErrors.length ? 's' : ''}:\n- ${allSSGErrors
|
|
82
125
|
.map((ssgError) => logger_1.default.path(ssgError.pathname))
|
|
@@ -93,24 +136,29 @@ async function generateStaticFiles({ pathnames, renderer, params, }) {
|
|
|
93
136
|
.value();
|
|
94
137
|
return { collectedData };
|
|
95
138
|
}
|
|
96
|
-
async function generateStaticFile({ pathname, renderer, params, }) {
|
|
139
|
+
async function generateStaticFile({ pathname, renderer, params, htmlMinifier, ssgTemplate, }) {
|
|
97
140
|
try {
|
|
98
141
|
// This only renders the app HTML
|
|
99
|
-
const result = await renderer({
|
|
142
|
+
const result = await renderer.render({
|
|
100
143
|
pathname,
|
|
101
144
|
});
|
|
102
145
|
// This renders the full page HTML, including head tags...
|
|
103
|
-
const fullPageHtml = (0,
|
|
146
|
+
const fullPageHtml = (0, ssgTemplate_1.renderSSGTemplate)({
|
|
104
147
|
params,
|
|
105
148
|
result,
|
|
149
|
+
ssgTemplate,
|
|
106
150
|
});
|
|
107
|
-
const
|
|
108
|
-
await writeStaticFile({
|
|
151
|
+
const minifierResult = await htmlMinifier.minify(fullPageHtml);
|
|
152
|
+
await (0, ssgUtils_1.writeStaticFile)({
|
|
109
153
|
pathname,
|
|
110
|
-
content,
|
|
154
|
+
content: minifierResult.code,
|
|
111
155
|
params,
|
|
112
156
|
});
|
|
113
|
-
return
|
|
157
|
+
return {
|
|
158
|
+
collectedData: result.collectedData,
|
|
159
|
+
// As of today, only the html minifier can emit SSG warnings
|
|
160
|
+
warnings: minifierResult.warnings,
|
|
161
|
+
};
|
|
114
162
|
}
|
|
115
163
|
catch (errorUnknown) {
|
|
116
164
|
const error = errorUnknown;
|
|
@@ -133,42 +181,3 @@ It might also require to wrap your client code in ${logger_1.default.code('useEf
|
|
|
133
181
|
}
|
|
134
182
|
return parts.join('\n');
|
|
135
183
|
}
|
|
136
|
-
async function generateHashRouterEntrypoint({ content, params, }) {
|
|
137
|
-
await writeStaticFile({
|
|
138
|
-
pathname: '/',
|
|
139
|
-
content,
|
|
140
|
-
params,
|
|
141
|
-
});
|
|
142
|
-
}
|
|
143
|
-
async function writeStaticFile({ content, pathname, params, }) {
|
|
144
|
-
function removeBaseUrl(p, baseUrl) {
|
|
145
|
-
return baseUrl === '/' ? p : p.replace(new RegExp(`^${baseUrl}`), '/');
|
|
146
|
-
}
|
|
147
|
-
const filename = pathnameToFilename({
|
|
148
|
-
pathname: removeBaseUrl(pathname, params.baseUrl),
|
|
149
|
-
trailingSlash: params.trailingSlash,
|
|
150
|
-
});
|
|
151
|
-
const filePath = path_1.default.join(params.outDir, filename);
|
|
152
|
-
await fs_extra_1.default.ensureDir(path_1.default.dirname(filePath));
|
|
153
|
-
await fs_extra_1.default.writeFile(filePath, content);
|
|
154
|
-
}
|
|
155
|
-
async function minifyHtml(html) {
|
|
156
|
-
try {
|
|
157
|
-
if (process.env.SKIP_HTML_MINIFICATION === 'true') {
|
|
158
|
-
return html;
|
|
159
|
-
}
|
|
160
|
-
// Minify html with https://github.com/DanielRuf/html-minifier-terser
|
|
161
|
-
return await (0, html_minifier_terser_1.minify)(html, {
|
|
162
|
-
removeComments: false,
|
|
163
|
-
removeRedundantAttributes: true,
|
|
164
|
-
removeEmptyAttributes: true,
|
|
165
|
-
removeScriptTypeAttributes: true,
|
|
166
|
-
removeStyleLinkTypeAttributes: true,
|
|
167
|
-
useShortDoctype: true,
|
|
168
|
-
minifyJS: true,
|
|
169
|
-
});
|
|
170
|
-
}
|
|
171
|
-
catch (err) {
|
|
172
|
-
throw new Error('HTML minification failed', { cause: err });
|
|
173
|
-
}
|
|
174
|
-
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { Props, RouterType } from '@docusaurus/types';
|
|
8
|
+
import type { SiteCollectedData } from '../common';
|
|
9
|
+
export declare function executeSSG({ props, serverBundlePath, clientManifestPath, router, }: {
|
|
10
|
+
props: Props;
|
|
11
|
+
serverBundlePath: string;
|
|
12
|
+
clientManifestPath: string;
|
|
13
|
+
router: RouterType;
|
|
14
|
+
}): Promise<{
|
|
15
|
+
collectedData: SiteCollectedData;
|
|
16
|
+
}>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.executeSSG = executeSSG;
|
|
10
|
+
const logger_1 = require("@docusaurus/logger");
|
|
11
|
+
const ssgParams_1 = require("./ssgParams");
|
|
12
|
+
const ssg_1 = require("./ssg");
|
|
13
|
+
const ssgTemplate_1 = require("./ssgTemplate");
|
|
14
|
+
const ssgUtils_1 = require("./ssgUtils");
|
|
15
|
+
// TODO Docusaurus v4 - introduce SSG worker threads
|
|
16
|
+
async function executeSSG({ props, serverBundlePath, clientManifestPath, router, }) {
|
|
17
|
+
const params = await (0, ssgParams_1.createSSGParams)({
|
|
18
|
+
serverBundlePath,
|
|
19
|
+
clientManifestPath,
|
|
20
|
+
props,
|
|
21
|
+
});
|
|
22
|
+
if (router === 'hash') {
|
|
23
|
+
logger_1.PerfLogger.start('Generate Hash Router entry point');
|
|
24
|
+
const content = await (0, ssgTemplate_1.renderHashRouterTemplate)({ params });
|
|
25
|
+
await (0, ssgUtils_1.generateHashRouterEntrypoint)({ content, params });
|
|
26
|
+
logger_1.PerfLogger.end('Generate Hash Router entry point');
|
|
27
|
+
return { collectedData: {} };
|
|
28
|
+
}
|
|
29
|
+
const ssgResult = await logger_1.PerfLogger.async('Generate static files', () => (0, ssg_1.generateStaticFiles)({
|
|
30
|
+
pathnames: props.routesPaths,
|
|
31
|
+
params,
|
|
32
|
+
}));
|
|
33
|
+
return ssgResult;
|
|
34
|
+
}
|
|
@@ -4,5 +4,8 @@
|
|
|
4
4
|
* This source code is licensed under the MIT license found in the
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
export type SSGNodeRequire = {
|
|
8
|
+
require: NodeJS.Require;
|
|
9
|
+
cleanup: () => void;
|
|
10
|
+
};
|
|
11
|
+
export declare function createSSGRequire(serverBundlePath: string): SSGNodeRequire;
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createSSGRequire = createSSGRequire;
|
|
10
|
+
const module_1 = require("module");
|
|
11
|
+
// The eval/vm.Script used for running the server bundle need a require() impl
|
|
12
|
+
// This impl has to be relative to the server bundler path
|
|
13
|
+
// This enables the server bundle to resolve relative paths such as:
|
|
14
|
+
// - require('./assets/js/some-chunk.123456.js')
|
|
15
|
+
//
|
|
16
|
+
// Unfortunately, Node.js vm.Script doesn't isolate memory / require.cache
|
|
17
|
+
// This means that if we build multiple Docusaurus localized sites in a row
|
|
18
|
+
// The Node.js require cache will keep growing and retain in memory the JS
|
|
19
|
+
// assets of the former SSG builds
|
|
20
|
+
// We have to clean up the node require cache manually to avoid leaking memory!
|
|
21
|
+
// See also https://x.com/sebastienlorber/status/1848399310116831702
|
|
22
|
+
function createSSGRequire(serverBundlePath) {
|
|
23
|
+
const realRequire = (0, module_1.createRequire)(serverBundlePath);
|
|
24
|
+
const allRequiredIds = [];
|
|
25
|
+
const ssgRequireFunction = (id) => {
|
|
26
|
+
const module = realRequire(id);
|
|
27
|
+
allRequiredIds.push(id);
|
|
28
|
+
return module;
|
|
29
|
+
};
|
|
30
|
+
const cleanup = () => {
|
|
31
|
+
allRequiredIds.forEach((id) => {
|
|
32
|
+
delete realRequire.cache[realRequire.resolve(id)];
|
|
33
|
+
});
|
|
34
|
+
};
|
|
35
|
+
ssgRequireFunction.resolve = realRequire.resolve;
|
|
36
|
+
ssgRequireFunction.cache = realRequire.cache;
|
|
37
|
+
ssgRequireFunction.extensions = realRequire.extensions;
|
|
38
|
+
ssgRequireFunction.main = realRequire.main;
|
|
39
|
+
return { require: ssgRequireFunction, cleanup };
|
|
40
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
3
|
+
*
|
|
4
|
+
* This source code is licensed under the MIT license found in the
|
|
5
|
+
* LICENSE file in the root directory of this source tree.
|
|
6
|
+
*/
|
|
7
|
+
import type { Manifest } from 'react-loadable-ssr-addon-v5-slorber';
|
|
8
|
+
import type { Props } from '@docusaurus/types';
|
|
9
|
+
import type { HtmlMinifierType } from '@docusaurus/bundler';
|
|
10
|
+
export type SSGParams = {
|
|
11
|
+
trailingSlash: boolean | undefined;
|
|
12
|
+
manifest: Manifest;
|
|
13
|
+
headTags: string;
|
|
14
|
+
preBodyTags: string;
|
|
15
|
+
postBodyTags: string;
|
|
16
|
+
outDir: string;
|
|
17
|
+
baseUrl: string;
|
|
18
|
+
noIndex: boolean;
|
|
19
|
+
DOCUSAURUS_VERSION: string;
|
|
20
|
+
htmlMinifierType: HtmlMinifierType;
|
|
21
|
+
serverBundlePath: string;
|
|
22
|
+
ssgTemplateContent: string;
|
|
23
|
+
};
|
|
24
|
+
export declare function createSSGParams({ props, serverBundlePath, clientManifestPath, }: {
|
|
25
|
+
props: Props;
|
|
26
|
+
serverBundlePath: string;
|
|
27
|
+
clientManifestPath: string;
|
|
28
|
+
}): Promise<SSGParams>;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Copyright (c) Facebook, Inc. and its affiliates.
|
|
4
|
+
*
|
|
5
|
+
* This source code is licensed under the MIT license found in the
|
|
6
|
+
* LICENSE file in the root directory of this source tree.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.createSSGParams = createSSGParams;
|
|
10
|
+
const tslib_1 = require("tslib");
|
|
11
|
+
const fs_extra_1 = tslib_1.__importDefault(require("fs-extra"));
|
|
12
|
+
const utils_1 = require("@docusaurus/utils");
|
|
13
|
+
const logger_1 = require("@docusaurus/logger");
|
|
14
|
+
const ssgTemplate_html_1 = tslib_1.__importDefault(require("./ssgTemplate.html"));
|
|
15
|
+
async function createSSGParams({ props, serverBundlePath, clientManifestPath, }) {
|
|
16
|
+
const manifest = await logger_1.PerfLogger.async('Read client manifest', () => fs_extra_1.default.readJSON(clientManifestPath, 'utf-8'));
|
|
17
|
+
const params = {
|
|
18
|
+
trailingSlash: props.siteConfig.trailingSlash,
|
|
19
|
+
outDir: props.outDir,
|
|
20
|
+
baseUrl: props.baseUrl,
|
|
21
|
+
manifest,
|
|
22
|
+
headTags: props.headTags,
|
|
23
|
+
preBodyTags: props.preBodyTags,
|
|
24
|
+
postBodyTags: props.postBodyTags,
|
|
25
|
+
ssgTemplateContent: props.siteConfig.ssrTemplate ?? ssgTemplate_html_1.default,
|
|
26
|
+
noIndex: props.siteConfig.noIndex,
|
|
27
|
+
DOCUSAURUS_VERSION: utils_1.DOCUSAURUS_VERSION,
|
|
28
|
+
serverBundlePath,
|
|
29
|
+
htmlMinifierType: props.siteConfig.future.experimental_faster
|
|
30
|
+
.swcHtmlMinimizer
|
|
31
|
+
? 'swc'
|
|
32
|
+
: 'terser',
|
|
33
|
+
};
|
|
34
|
+
// Useless but ensures that SSG params remain serializable
|
|
35
|
+
return structuredClone(params);
|
|
36
|
+
}
|