@docusaurus/core 0.0.0-4523 → 0.0.0-4524
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/lib/babel/preset.js +5 -4
- package/lib/client/PendingNavigation.d.ts +3 -3
- package/lib/client/PendingNavigation.js +2 -2
- package/lib/client/baseUrlIssueBanner/BaseUrlIssueBanner.d.ts +8 -0
- package/lib/client/baseUrlIssueBanner/BaseUrlIssueBanner.js +8 -5
- package/lib/client/clientEntry.js +5 -3
- package/lib/client/docusaurus.js +5 -3
- package/lib/client/exports/ComponentCreator.js +4 -1
- package/lib/client/exports/Interpolate.js +9 -13
- package/lib/client/exports/Link.js +4 -3
- package/lib/client/exports/Translate.js +2 -1
- package/lib/client/exports/browserContext.js +2 -1
- package/lib/client/preload.d.ts +2 -1
- package/lib/client/preload.js +2 -1
- package/lib/client/serverEntry.js +1 -1
- package/lib/client/theme-fallback/Error/index.js +5 -3
- package/lib/commands/build.js +15 -17
- package/lib/commands/deploy.js +3 -3
- package/lib/commands/serve.js +2 -2
- package/lib/commands/swizzle.js +4 -2
- package/lib/commands/writeHeadingIds.js +10 -11
- package/lib/commands/writeTranslations.js +7 -4
- package/lib/server/brokenLinks.js +14 -9
- package/lib/server/duplicateRoutes.js +2 -4
- package/lib/server/i18n.js +11 -15
- package/lib/server/index.js +9 -7
- package/lib/server/plugins/index.js +6 -6
- package/lib/server/plugins/init.js +12 -22
- package/lib/server/presets/index.js +2 -2
- package/lib/server/routes.js +2 -1
- package/lib/server/themes/index.js +3 -2
- package/lib/server/translations/translations.js +2 -4
- package/lib/server/translations/translationsExtractor.js +17 -14
- package/lib/server/versions/index.js +4 -2
- package/lib/webpack/base.js +19 -11
- package/lib/webpack/client.js +5 -2
- package/lib/webpack/plugins/ChunkAssetPlugin.d.ts +11 -0
- package/lib/webpack/plugins/ChunkAssetPlugin.js +17 -10
- package/lib/webpack/plugins/CleanWebpackPlugin.d.ts +4 -3
- package/lib/webpack/plugins/CleanWebpackPlugin.js +2 -1
- package/lib/webpack/server.js +4 -3
- package/lib/webpack/utils.js +8 -9
- package/package.json +10 -10
package/lib/babel/preset.js
CHANGED
|
@@ -11,7 +11,8 @@ const path_1 = (0, tslib_1.__importDefault)(require("path"));
|
|
|
11
11
|
function getTransformOptions(isServer) {
|
|
12
12
|
const absoluteRuntimePath = path_1.default.dirname(require.resolve(`@babel/runtime/package.json`));
|
|
13
13
|
return {
|
|
14
|
-
// All optional newlines and whitespace will be omitted when generating code
|
|
14
|
+
// All optional newlines and whitespace will be omitted when generating code
|
|
15
|
+
// in compact mode
|
|
15
16
|
compact: true,
|
|
16
17
|
presets: [
|
|
17
18
|
isServer
|
|
@@ -46,9 +47,9 @@ function getTransformOptions(isServer) {
|
|
|
46
47
|
{
|
|
47
48
|
corejs: false,
|
|
48
49
|
helpers: true,
|
|
49
|
-
// By default, it assumes @babel/runtime@7.0.0. Since we use >7.0.0,
|
|
50
|
-
// explicitly specify the version so that it can reuse the
|
|
51
|
-
// See https://github.com/babel/babel/issues/10261
|
|
50
|
+
// By default, it assumes @babel/runtime@7.0.0. Since we use >7.0.0,
|
|
51
|
+
// better to explicitly specify the version so that it can reuse the
|
|
52
|
+
// helper better. See https://github.com/babel/babel/issues/10261
|
|
52
53
|
// eslint-disable-next-line @typescript-eslint/no-var-requires, global-require
|
|
53
54
|
version: require('@babel/runtime/package.json').version,
|
|
54
55
|
regenerator: true,
|
|
@@ -23,9 +23,9 @@ declare class PendingNavigation extends React.Component<Props, State> {
|
|
|
23
23
|
progressBarTimeout: NodeJS.Timeout | null;
|
|
24
24
|
constructor(props: Props);
|
|
25
25
|
shouldComponentUpdate(nextProps: Props, nextState: State): boolean;
|
|
26
|
-
clearProgressBarTimeout
|
|
27
|
-
startProgressBar
|
|
28
|
-
stopProgressBar
|
|
26
|
+
private clearProgressBarTimeout;
|
|
27
|
+
private startProgressBar;
|
|
28
|
+
private stopProgressBar;
|
|
29
29
|
render(): JSX.Element;
|
|
30
30
|
}
|
|
31
31
|
declare const _default: React.ComponentClass<Pick<Props, "routes" | "delay">, any> & import("react-router").WithRouterStatics<typeof PendingNavigation>;
|
|
@@ -27,8 +27,8 @@ class PendingNavigation extends React.Component {
|
|
|
27
27
|
shouldComponentUpdate(nextProps, nextState) {
|
|
28
28
|
const routeDidChange = nextProps.location !== this.props.location;
|
|
29
29
|
const { routes, delay } = this.props;
|
|
30
|
-
// If `routeDidChange` is true, means the router is trying to navigate to a
|
|
31
|
-
// route. We will preload the new route.
|
|
30
|
+
// If `routeDidChange` is true, means the router is trying to navigate to a
|
|
31
|
+
// new route. We will preload the new route.
|
|
32
32
|
if (routeDidChange) {
|
|
33
33
|
const nextLocation = normalizeLocation(nextProps.location);
|
|
34
34
|
this.startProgressBar(delay);
|
|
@@ -11,4 +11,12 @@ declare global {
|
|
|
11
11
|
__DOCUSAURUS_INSERT_BASEURL_BANNER: boolean;
|
|
12
12
|
}
|
|
13
13
|
}
|
|
14
|
+
/**
|
|
15
|
+
* We want to help the users with a bad baseUrl configuration (very common
|
|
16
|
+
* error) Help message is inlined, and hidden if JS or CSS is able to load
|
|
17
|
+
* Note: it might create false positives (ie network failures): not a big deal
|
|
18
|
+
* Note: we only inline this for the homepage to avoid polluting all the site's
|
|
19
|
+
* pages
|
|
20
|
+
* @see https://github.com/facebook/docusaurus/pull/3621
|
|
21
|
+
*/
|
|
14
22
|
export default function BaseUrlIssueBanner(): JSX.Element | null;
|
|
@@ -74,11 +74,14 @@ function BaseUrlIssueBannerEnabled() {
|
|
|
74
74
|
<div id={BannerContainerId}/>
|
|
75
75
|
</>);
|
|
76
76
|
}
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
77
|
+
/**
|
|
78
|
+
* We want to help the users with a bad baseUrl configuration (very common
|
|
79
|
+
* error) Help message is inlined, and hidden if JS or CSS is able to load
|
|
80
|
+
* Note: it might create false positives (ie network failures): not a big deal
|
|
81
|
+
* Note: we only inline this for the homepage to avoid polluting all the site's
|
|
82
|
+
* pages
|
|
83
|
+
* @see https://github.com/facebook/docusaurus/pull/3621
|
|
84
|
+
*/
|
|
82
85
|
export default function BaseUrlIssueBanner() {
|
|
83
86
|
const { siteConfig: { baseUrl, baseUrlIssueBanner }, } = useDocusaurusContext();
|
|
84
87
|
const { pathname } = useLocation();
|
|
@@ -12,12 +12,14 @@ import ExecutionEnvironment from './exports/ExecutionEnvironment';
|
|
|
12
12
|
import App from './App';
|
|
13
13
|
import preload from './preload';
|
|
14
14
|
import docusaurus from './docusaurus';
|
|
15
|
-
// Client-side render (e.g: running in browser) to become single-page
|
|
15
|
+
// Client-side render (e.g: running in browser) to become single-page
|
|
16
|
+
// application (SPA).
|
|
16
17
|
if (ExecutionEnvironment.canUseDOM) {
|
|
17
18
|
window.docusaurus = docusaurus;
|
|
18
|
-
// For production, attempt to hydrate existing markup for performant
|
|
19
|
+
// For production, attempt to hydrate existing markup for performant
|
|
20
|
+
// first-load experience.
|
|
19
21
|
// For development, there is no existing markup so we had to render it.
|
|
20
|
-
//
|
|
22
|
+
// We also preload async component to avoid first-load loading screen.
|
|
21
23
|
const renderMethod = process.env.NODE_ENV === 'production' ? hydrate : render;
|
|
22
24
|
preload(routes, window.location.pathname).then(() => {
|
|
23
25
|
renderMethod(<BrowserRouter>
|
package/lib/client/docusaurus.js
CHANGED
|
@@ -41,11 +41,13 @@ const docusaurus = {
|
|
|
41
41
|
const chunkNamesNeeded = matches.flatMap((match) => getChunkNamesToLoad(match.route.path));
|
|
42
42
|
// Prefetch all webpack chunk assets file needed.
|
|
43
43
|
chunkNamesNeeded.forEach((chunkName) => {
|
|
44
|
-
// "__webpack_require__.gca" is a custom function provided by
|
|
45
|
-
// Pass it the chunkName or chunkId you want to load and
|
|
44
|
+
// "__webpack_require__.gca" is a custom function provided by
|
|
45
|
+
// ChunkAssetPlugin. Pass it the chunkName or chunkId you want to load and
|
|
46
|
+
// it will return the URL for that chunk.
|
|
46
47
|
// eslint-disable-next-line camelcase
|
|
47
48
|
const chunkAsset = __webpack_require__.gca(chunkName);
|
|
48
|
-
// In some cases, webpack might decide to optimize further & hence the
|
|
49
|
+
// In some cases, webpack might decide to optimize further & hence the
|
|
50
|
+
// chunk assets are merged to another chunk/previous chunk.
|
|
49
51
|
// Hence, we can safely filter it out/don't need to load it.
|
|
50
52
|
if (chunkAsset && !/undefined/.test(chunkAsset)) {
|
|
51
53
|
prefetchHelper(chunkAsset);
|
|
@@ -32,7 +32,10 @@ function ComponentCreator(path, hash) {
|
|
|
32
32
|
content.foo: () => import('./doc1.md'),
|
|
33
33
|
}
|
|
34
34
|
- optsModules: ['./Pages.js', './doc1.md']
|
|
35
|
-
- optsWebpack: [
|
|
35
|
+
- optsWebpack: [
|
|
36
|
+
require.resolveWeak('./Pages.js'),
|
|
37
|
+
require.resolveWeak('./doc1.md'),
|
|
38
|
+
]
|
|
36
39
|
*/
|
|
37
40
|
const flatChunkNames = flat(chunkNames);
|
|
38
41
|
Object.keys(flatChunkNames).forEach((key) => {
|
|
@@ -26,30 +26,26 @@ export function interpolate(text, values) {
|
|
|
26
26
|
elements.push(element);
|
|
27
27
|
return ValueFoundMarker;
|
|
28
28
|
}
|
|
29
|
-
|
|
30
|
-
return match; // no match? add warning?
|
|
31
|
-
}
|
|
29
|
+
return match; // no match? add warning?
|
|
32
30
|
});
|
|
33
31
|
// No interpolation to be done: just return the text
|
|
34
32
|
if (elements.length === 0) {
|
|
35
33
|
return text;
|
|
36
34
|
}
|
|
37
35
|
// Basic string interpolation: returns interpolated string
|
|
38
|
-
|
|
36
|
+
if (elements.every((el) => typeof el === 'string')) {
|
|
39
37
|
return processedText
|
|
40
38
|
.split(ValueFoundMarker)
|
|
41
39
|
.reduce((str, value, index) => { var _a; return str.concat(value).concat((_a = elements[index]) !== null && _a !== void 0 ? _a : ''); }, '');
|
|
42
40
|
}
|
|
43
41
|
// JSX interpolation: returns ReactNode
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
], []);
|
|
52
|
-
}
|
|
42
|
+
return processedText.split(ValueFoundMarker).reduce((array, value, index) => [
|
|
43
|
+
...array,
|
|
44
|
+
<React.Fragment key={index}>
|
|
45
|
+
{value}
|
|
46
|
+
{elements[index]}
|
|
47
|
+
</React.Fragment>,
|
|
48
|
+
], []);
|
|
53
49
|
}
|
|
54
50
|
export default function Interpolate({ children, values, }) {
|
|
55
51
|
if (typeof children !== 'string') {
|
|
@@ -57,7 +57,7 @@ function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLi
|
|
|
57
57
|
ioRef.current = new window.IntersectionObserver((entries) => {
|
|
58
58
|
entries.forEach((entry) => {
|
|
59
59
|
if (el === entry.target) {
|
|
60
|
-
// If element is in viewport, stop
|
|
60
|
+
// If element is in viewport, stop observing and run callback.
|
|
61
61
|
// https://developer.mozilla.org/en-US/docs/Web/API/Intersection_Observer_API
|
|
62
62
|
if (entry.isIntersecting || entry.intersectionRatio > 0) {
|
|
63
63
|
ioRef.current.unobserve(el);
|
|
@@ -72,7 +72,7 @@ function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLi
|
|
|
72
72
|
};
|
|
73
73
|
const handleRef = (ref) => {
|
|
74
74
|
if (IOSupported && ref && isInternal) {
|
|
75
|
-
// If IO supported and element reference found,
|
|
75
|
+
// If IO supported and element reference found, set up Observer.
|
|
76
76
|
handleIntersection(ref, () => {
|
|
77
77
|
if (targetLink != null) {
|
|
78
78
|
window.docusaurus.prefetch(targetLink);
|
|
@@ -109,7 +109,8 @@ function Link({ isNavLink, to, href, activeClassName, isActive, 'data-noBrokenLi
|
|
|
109
109
|
// eslint-disable-next-line jsx-a11y/anchor-has-content
|
|
110
110
|
<a href={targetLink} {...(targetLinkUnprefixed &&
|
|
111
111
|
!isInternal && { target: '_blank', rel: 'noopener noreferrer' })} {...props}/>) : (<LinkComponent {...props} onMouseEnter={onMouseEnter} innerRef={handleRef} to={targetLink || ''}
|
|
112
|
-
// avoid "React does not recognize the `activeClassName` prop on a DOM
|
|
112
|
+
// avoid "React does not recognize the `activeClassName` prop on a DOM
|
|
113
|
+
// element"
|
|
113
114
|
{...(isNavLink && { isActive, activeClassName })}/>);
|
|
114
115
|
}
|
|
115
116
|
export default Link;
|
|
@@ -22,7 +22,8 @@ export function translate({ message, id }, values) {
|
|
|
22
22
|
return interpolate(localizedMessage, values);
|
|
23
23
|
}
|
|
24
24
|
// Maybe we'll want to improve this component with additional features
|
|
25
|
-
// Like toggling a translation mode that adds a little translation button near
|
|
25
|
+
// Like toggling a translation mode that adds a little translation button near
|
|
26
|
+
// the text?
|
|
26
27
|
export default function Translate({ children, id, values, }) {
|
|
27
28
|
if (children && typeof children !== 'string') {
|
|
28
29
|
console.warn('Illegal <Translate> children', children);
|
|
@@ -10,7 +10,8 @@ import React, { useEffect, useState } from 'react';
|
|
|
10
10
|
// On first client-side render, we need to render exactly as the server rendered
|
|
11
11
|
// isBrowser is set to true only after a successful hydration
|
|
12
12
|
// Note, isBrowser is not part of useDocusaurusContext() for perf reasons
|
|
13
|
-
// Using useDocusaurusContext() (much more common need) should not trigger
|
|
13
|
+
// Using useDocusaurusContext() (much more common need) should not trigger
|
|
14
|
+
// re-rendering after a successful hydration
|
|
14
15
|
export const Context = React.createContext(false);
|
|
15
16
|
export function BrowserContextProvider({ children, }) {
|
|
16
17
|
const [isBrowser, setIsBrowser] = useState(false);
|
package/lib/client/preload.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { type RouteConfig } from 'react-router-config';
|
|
8
8
|
/**
|
|
9
9
|
* Helper function to make sure all async components for that particular route
|
|
10
|
-
* is preloaded before rendering. This is especially useful to avoid loading
|
|
10
|
+
* is preloaded before rendering. This is especially useful to avoid loading
|
|
11
|
+
* screens.
|
|
11
12
|
*
|
|
12
13
|
* @param routes react-router-config
|
|
13
14
|
* @param pathname the route pathname, example: /docs/installation
|
package/lib/client/preload.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
import { matchRoutes } from 'react-router-config';
|
|
8
8
|
/**
|
|
9
9
|
* Helper function to make sure all async components for that particular route
|
|
10
|
-
* is preloaded before rendering. This is especially useful to avoid loading
|
|
10
|
+
* is preloaded before rendering. This is especially useful to avoid loading
|
|
11
|
+
* screens.
|
|
11
12
|
*
|
|
12
13
|
* @param routes react-router-config
|
|
13
14
|
* @param pathname the route pathname, example: /docs/installation
|
|
@@ -46,7 +46,7 @@ It might also require to wrap your client code in code=${'useEffect'} hook and/o
|
|
|
46
46
|
throw new Error('Server-side rendering fails due to the error above.');
|
|
47
47
|
}
|
|
48
48
|
}
|
|
49
|
-
// Renderer for static-site-generator-webpack-plugin (async rendering
|
|
49
|
+
// Renderer for static-site-generator-webpack-plugin (async rendering).
|
|
50
50
|
async function doRender(locals) {
|
|
51
51
|
const { routesLocation, headTags, preBodyTags, postBodyTags, onLinksCollected, baseUrl, ssrTemplate, noIndex, } = locals;
|
|
52
52
|
const location = routesLocation[locals.path];
|
|
@@ -25,10 +25,12 @@ function ErrorDisplay({ error, tryAgain }) {
|
|
|
25
25
|
</div>);
|
|
26
26
|
}
|
|
27
27
|
function Error({ error, tryAgain }) {
|
|
28
|
-
// We wrap the error in its own error boundary because the layout can actually
|
|
29
|
-
// Only the ErrorDisplay component is simple enough to be
|
|
28
|
+
// We wrap the error in its own error boundary because the layout can actually
|
|
29
|
+
// throw too... Only the ErrorDisplay component is simple enough to be
|
|
30
|
+
// considered safe to never throw
|
|
30
31
|
return (<ErrorBoundary
|
|
31
|
-
// Note: we display the original error here, not the error that we
|
|
32
|
+
// Note: we display the original error here, not the error that we
|
|
33
|
+
// captured in this extra error boundary
|
|
32
34
|
fallback={() => <ErrorDisplay error={error} tryAgain={tryAgain}/>}>
|
|
33
35
|
<Layout title="Page Error">
|
|
34
36
|
<ErrorDisplay error={error} tryAgain={tryAgain}/>
|
package/lib/commands/build.js
CHANGED
|
@@ -58,22 +58,20 @@ forceTerminate = true) {
|
|
|
58
58
|
if (cliOptions.locale) {
|
|
59
59
|
return tryToBuildLocale({ locale: cliOptions.locale, isLastLocale: true });
|
|
60
60
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
logger_1.default.info `Website will be built for all these locales: ${i18n.locales}`;
|
|
64
|
-
}
|
|
65
|
-
// We need the default locale to always be the 1st in the list
|
|
66
|
-
// If we build it last, it would "erase" the localized sites built in sub-folders
|
|
67
|
-
const orderedLocales = [
|
|
68
|
-
i18n.defaultLocale,
|
|
69
|
-
...i18n.locales.filter((locale) => locale !== i18n.defaultLocale),
|
|
70
|
-
];
|
|
71
|
-
const results = await (0, utils_2.mapAsyncSequential)(orderedLocales, (locale) => {
|
|
72
|
-
const isLastLocale = orderedLocales.indexOf(locale) === orderedLocales.length - 1;
|
|
73
|
-
return tryToBuildLocale({ locale, isLastLocale });
|
|
74
|
-
});
|
|
75
|
-
return results[0];
|
|
61
|
+
if (i18n.locales.length > 1) {
|
|
62
|
+
logger_1.default.info `Website will be built for all these locales: ${i18n.locales}`;
|
|
76
63
|
}
|
|
64
|
+
// We need the default locale to always be the 1st in the list. If we build it
|
|
65
|
+
// last, it would "erase" the localized sites built in sub-folders
|
|
66
|
+
const orderedLocales = [
|
|
67
|
+
i18n.defaultLocale,
|
|
68
|
+
...i18n.locales.filter((locale) => locale !== i18n.defaultLocale),
|
|
69
|
+
];
|
|
70
|
+
const results = await (0, utils_2.mapAsyncSequential)(orderedLocales, (locale) => {
|
|
71
|
+
const isLastLocale = orderedLocales.indexOf(locale) === orderedLocales.length - 1;
|
|
72
|
+
return tryToBuildLocale({ locale, isLastLocale });
|
|
73
|
+
});
|
|
74
|
+
return results[0];
|
|
77
75
|
}
|
|
78
76
|
exports.default = build;
|
|
79
77
|
async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLastLocale, }) {
|
|
@@ -93,7 +91,8 @@ async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLast
|
|
|
93
91
|
plugins: [
|
|
94
92
|
// Remove/clean build folders before building bundles.
|
|
95
93
|
new CleanWebpackPlugin_1.default({ verbose: false }),
|
|
96
|
-
// Visualize size of webpack output files with an interactive zoomable
|
|
94
|
+
// Visualize size of webpack output files with an interactive zoomable
|
|
95
|
+
// tree map.
|
|
97
96
|
cliOptions.bundleAnalyzer && new webpack_bundle_analyzer_1.BundleAnalyzerPlugin(),
|
|
98
97
|
// Generate client manifests file that will be used for server bundle.
|
|
99
98
|
new react_loadable_ssr_addon_v5_slorber_1.default({
|
|
@@ -153,7 +152,6 @@ async function buildLocale({ siteDir, locale, cliOptions, forceTerminate, isLast
|
|
|
153
152
|
if (!plugin.postBuild) {
|
|
154
153
|
return;
|
|
155
154
|
}
|
|
156
|
-
// The plugin may reference `this`. We manually bind it again to prevent any bugs.
|
|
157
155
|
await plugin.postBuild({ ...props, content: plugin.content });
|
|
158
156
|
}));
|
|
159
157
|
await (0, brokenLinks_1.handleBrokenLinks)({
|
package/lib/commands/deploy.js
CHANGED
|
@@ -114,8 +114,8 @@ This behavior can have SEO impacts and create relative link issues.
|
|
|
114
114
|
shelljs_1.default.echo('Skipping deploy on a pull request.');
|
|
115
115
|
shelljs_1.default.exit(0);
|
|
116
116
|
}
|
|
117
|
-
// github.io indicates organization repos that deploy via default branch.
|
|
118
|
-
// Organization deploys looks like:
|
|
117
|
+
// github.io indicates organization repos that deploy via default branch.
|
|
118
|
+
// All others use gh-pages. Organization deploys looks like:
|
|
119
119
|
// - Git repo: https://github.com/<organization>/<organization>.github.io
|
|
120
120
|
// - Site url: https://<organization>.github.io
|
|
121
121
|
const isGitHubPagesOrganizationDeploy = projectName.includes('.github.io');
|
|
@@ -196,7 +196,7 @@ You can also set the deploymentBranch property in docusaurus.config.js .`);
|
|
|
196
196
|
}
|
|
197
197
|
};
|
|
198
198
|
if (!cliOptions.skipBuild) {
|
|
199
|
-
// Build
|
|
199
|
+
// Build site, then push to deploymentBranch branch of specified repo.
|
|
200
200
|
try {
|
|
201
201
|
await runDeploy(await (0, build_1.default)(siteDir, cliOptions, false));
|
|
202
202
|
}
|
package/lib/commands/serve.js
CHANGED
|
@@ -44,8 +44,8 @@ async function serve(siteDir, cliOptions) {
|
|
|
44
44
|
res.end();
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
// Remove baseUrl before calling serveHandler
|
|
48
|
-
//
|
|
47
|
+
// Remove baseUrl before calling serveHandler, because /baseUrl/ should
|
|
48
|
+
// serve /build/index.html, not /build/baseUrl/index.html (does not exist)
|
|
49
49
|
req.url = (_b = req.url) === null || _b === void 0 ? void 0 : _b.replace(baseUrl, '/');
|
|
50
50
|
(0, serve_handler_1.default)(req, res, {
|
|
51
51
|
cleanUrls: true,
|
package/lib/commands/swizzle.js
CHANGED
|
@@ -185,7 +185,8 @@ async function swizzle(siteDir, themeName, componentName, typescript, danger) {
|
|
|
185
185
|
let score = formattedComponentName.length;
|
|
186
186
|
components.forEach((component) => {
|
|
187
187
|
if (component.toLowerCase() === formattedComponentName.toLowerCase()) {
|
|
188
|
-
// may be components with same lowercase key, try to match closest
|
|
188
|
+
// may be components with same lowercase key, try to match closest
|
|
189
|
+
// component
|
|
189
190
|
const currentScore = (0, leven_1.default)(formattedComponentName, component);
|
|
190
191
|
if (currentScore < score) {
|
|
191
192
|
score = currentScore;
|
|
@@ -202,7 +203,8 @@ async function swizzle(siteDir, themeName, componentName, typescript, danger) {
|
|
|
202
203
|
let fromPath = path_1.default.join(themePath, mostSuitableComponent);
|
|
203
204
|
let toPath = path_1.default.resolve(siteDir, utils_1.THEME_PATH, mostSuitableComponent);
|
|
204
205
|
// Handle single TypeScript/JavaScript file only.
|
|
205
|
-
// E.g: if <fromPath> does not exist, we try to swizzle
|
|
206
|
+
// E.g: if <fromPath> does not exist, we try to swizzle
|
|
207
|
+
// <fromPath>.(ts|tsx|js) instead
|
|
206
208
|
if (!fs_extra_1.default.existsSync(fromPath)) {
|
|
207
209
|
if (fs_extra_1.default.existsSync(`${fromPath}.ts`)) {
|
|
208
210
|
[fromPath, toPath] = [`${fromPath}.ts`, `${toPath}.ts`];
|
|
@@ -48,9 +48,7 @@ function transformMarkdownLine(line, slugger, options) {
|
|
|
48
48
|
if (line.startsWith('##')) {
|
|
49
49
|
return transformMarkdownHeadingLine(line, slugger, options);
|
|
50
50
|
}
|
|
51
|
-
|
|
52
|
-
return line;
|
|
53
|
-
}
|
|
51
|
+
return line;
|
|
54
52
|
}
|
|
55
53
|
function transformMarkdownLines(lines, options) {
|
|
56
54
|
let inCode = false;
|
|
@@ -60,12 +58,10 @@ function transformMarkdownLines(lines, options) {
|
|
|
60
58
|
inCode = !inCode;
|
|
61
59
|
return line;
|
|
62
60
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return line;
|
|
66
|
-
}
|
|
67
|
-
return transformMarkdownLine(line, slugger, options);
|
|
61
|
+
if (inCode) {
|
|
62
|
+
return line;
|
|
68
63
|
}
|
|
64
|
+
return transformMarkdownLine(line, slugger, options);
|
|
69
65
|
});
|
|
70
66
|
}
|
|
71
67
|
function transformMarkdownContent(content, options) {
|
|
@@ -81,9 +77,12 @@ async function transformMarkdownFile(filepath, options) {
|
|
|
81
77
|
}
|
|
82
78
|
return undefined;
|
|
83
79
|
}
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
80
|
+
/**
|
|
81
|
+
* We only handle the "paths to watch" because these are the paths where the
|
|
82
|
+
* markdown files are. Also we don't want to transform the site md docs that do
|
|
83
|
+
* not belong to a content plugin. For example ./README.md should not be
|
|
84
|
+
* transformed
|
|
85
|
+
*/
|
|
87
86
|
async function getPathsToWatch(siteDir) {
|
|
88
87
|
const context = await (0, server_1.loadContext)(siteDir);
|
|
89
88
|
const pluginConfigs = (0, server_1.loadPluginConfigs)(context);
|
|
@@ -13,10 +13,13 @@ const init_1 = (0, tslib_1.__importDefault)(require("../server/plugins/init"));
|
|
|
13
13
|
const translations_1 = require("../server/translations/translations");
|
|
14
14
|
const translationsExtractor_1 = require("../server/translations/translationsExtractor");
|
|
15
15
|
const utils_1 = require("../webpack/utils");
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
/**
|
|
17
|
+
* This is a hack, so that @docusaurus/theme-common translations are extracted!
|
|
18
|
+
* A theme doesn't have a way to express that one of its dependency (like
|
|
19
|
+
* @docusaurus/theme-common) also has translations to extract.
|
|
20
|
+
* Instead of introducing a new lifecycle (like `getThemeTranslationPaths()`?)
|
|
21
|
+
* We just make an exception and assume that user is using an official theme
|
|
22
|
+
*/
|
|
20
23
|
async function getExtraSourceCodeFilePaths() {
|
|
21
24
|
try {
|
|
22
25
|
const themeCommonSourceDir = path_1.default.dirname(require.resolve('@docusaurus/theme-common/lib'));
|
|
@@ -39,10 +39,12 @@ function getPageBrokenLinks({ pagePath, pageLinks, routes, }) {
|
|
|
39
39
|
}
|
|
40
40
|
return pageLinks.map(resolveLink).filter((l) => isBrokenLink(l.resolvedLink));
|
|
41
41
|
}
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
/**
|
|
43
|
+
* The route defs can be recursive, and have a parent match-all route. We don't
|
|
44
|
+
* want to match broken links like /docs/brokenLink against /docs/*. For this
|
|
45
|
+
* reason, we only consider the "final routes", that do not have subroutes.
|
|
46
|
+
* We also need to remove the match all 404 route
|
|
47
|
+
*/
|
|
46
48
|
function filterIntermediateRoutes(routesInput) {
|
|
47
49
|
const routesWithout404 = routesInput.filter((route) => route.path !== '*');
|
|
48
50
|
return (0, utils_2.getAllFinalRoutes)(routesWithout404);
|
|
@@ -67,9 +69,11 @@ function getBrokenLinksErrorMessage(allBrokenLinks) {
|
|
|
67
69
|
.map(brokenLinkMessage)
|
|
68
70
|
.join('\n -> linking to ')}`;
|
|
69
71
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
72
|
+
/**
|
|
73
|
+
* If there's a broken link appearing very often, it is probably a broken link
|
|
74
|
+
* on the layout. Add an additional message in such case to help user figure
|
|
75
|
+
* this out. See https://github.com/facebook/docusaurus/issues/3567#issuecomment-706973805
|
|
76
|
+
*/
|
|
73
77
|
function getLayoutBrokenLinksHelpMessage() {
|
|
74
78
|
const flatList = Object.entries(allBrokenLinks).flatMap(([pagePage, brokenLinks]) => brokenLinks.map((brokenLink) => ({ pagePage, brokenLink })));
|
|
75
79
|
const countedBrokenLinks = (0, lodash_1.countBy)(flatList, (item) => item.brokenLink.link);
|
|
@@ -121,8 +125,9 @@ async function handleBrokenLinks({ allCollectedLinks, onBrokenLinks, routes, bas
|
|
|
121
125
|
if (onBrokenLinks === 'ignore') {
|
|
122
126
|
return;
|
|
123
127
|
}
|
|
124
|
-
// If we link to a file like /myFile.zip, and the file actually exist for the
|
|
125
|
-
//
|
|
128
|
+
// If we link to a file like /myFile.zip, and the file actually exist for the
|
|
129
|
+
// file system. It is not a broken link, it may simply be a link to an
|
|
130
|
+
// existing static file...
|
|
126
131
|
const allCollectedLinksFiltered = await filterExistingFileLinks({
|
|
127
132
|
allCollectedLinks,
|
|
128
133
|
baseUrl,
|
|
@@ -16,10 +16,8 @@ function getAllDuplicateRoutes(pluginsRouteConfigs) {
|
|
|
16
16
|
if (Object.prototype.hasOwnProperty.call(seenRoutes, route)) {
|
|
17
17
|
return true;
|
|
18
18
|
}
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
return false;
|
|
22
|
-
}
|
|
19
|
+
seenRoutes[route] = true;
|
|
20
|
+
return false;
|
|
23
21
|
});
|
|
24
22
|
}
|
|
25
23
|
exports.getAllDuplicateRoutes = getAllDuplicateRoutes;
|
package/lib/server/i18n.js
CHANGED
|
@@ -61,22 +61,18 @@ function localizePath({ pathType, path: originalPath, i18n, options = {}, }) {
|
|
|
61
61
|
? // By default, we don't localize the path of defaultLocale
|
|
62
62
|
i18n.currentLocale !== i18n.defaultLocale
|
|
63
63
|
: options.localizePath;
|
|
64
|
-
if (shouldLocalizePath) {
|
|
65
|
-
// FS paths need special care, for Windows support
|
|
66
|
-
if (pathType === 'fs') {
|
|
67
|
-
return path_1.default.join(originalPath, path_1.default.sep, i18n.currentLocale, path_1.default.sep);
|
|
68
|
-
}
|
|
69
|
-
// Url paths
|
|
70
|
-
else if (pathType === 'url') {
|
|
71
|
-
return (0, utils_1.normalizeUrl)([originalPath, '/', i18n.currentLocale, '/']);
|
|
72
|
-
}
|
|
73
|
-
// should never happen
|
|
74
|
-
else {
|
|
75
|
-
throw new Error(`Unhandled path type "${pathType}".`);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
else {
|
|
64
|
+
if (!shouldLocalizePath) {
|
|
79
65
|
return originalPath;
|
|
80
66
|
}
|
|
67
|
+
// FS paths need special care, for Windows support
|
|
68
|
+
if (pathType === 'fs') {
|
|
69
|
+
return path_1.default.join(originalPath, path_1.default.sep, i18n.currentLocale, path_1.default.sep);
|
|
70
|
+
}
|
|
71
|
+
// Url paths
|
|
72
|
+
if (pathType === 'url') {
|
|
73
|
+
return (0, utils_1.normalizeUrl)([originalPath, '/', i18n.currentLocale, '/']);
|
|
74
|
+
}
|
|
75
|
+
// should never happen
|
|
76
|
+
throw new Error(`Unhandled path type "${pathType}".`);
|
|
81
77
|
}
|
|
82
78
|
exports.localizePath = localizePath;
|
package/lib/server/index.js
CHANGED
|
@@ -150,10 +150,12 @@ function createBootstrapPlugin({ siteConfig, }) {
|
|
|
150
150
|
},
|
|
151
151
|
};
|
|
152
152
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
/**
|
|
154
|
+
* Configure Webpack fallback mdx loader for md/mdx files out of content-plugin
|
|
155
|
+
* folders. Adds a "fallback" mdx loader for mdx files that are not processed by
|
|
156
|
+
* content plugins. This allows to do things such as importing repo/README.md as
|
|
157
|
+
* a partial from another doc. Not ideal solution, but good enough for now
|
|
158
|
+
*/
|
|
157
159
|
function createMDXFallbackPlugin({ siteDir, siteConfig, }) {
|
|
158
160
|
return {
|
|
159
161
|
name: 'docusaurus-mdx-fallback-plugin',
|
|
@@ -161,9 +163,9 @@ function createMDXFallbackPlugin({ siteDir, siteConfig, }) {
|
|
|
161
163
|
options: {},
|
|
162
164
|
version: { type: 'synthetic' },
|
|
163
165
|
configureWebpack(config, isServer, { getJSLoader }) {
|
|
164
|
-
// We need the mdx fallback loader to exclude files that were already
|
|
165
|
-
// This works, but a bit
|
|
166
|
-
// Not sure there's a way to handle that differently in webpack
|
|
166
|
+
// We need the mdx fallback loader to exclude files that were already
|
|
167
|
+
// processed by content plugins mdx loaders. This works, but a bit
|
|
168
|
+
// hacky... Not sure there's a way to handle that differently in webpack
|
|
167
169
|
function getMDXFallbackExcludedPaths() {
|
|
168
170
|
var _a;
|
|
169
171
|
const rules = (_a = config === null || config === void 0 ? void 0 : config.module) === null || _a === void 0 ? void 0 : _a.rules;
|
|
@@ -57,9 +57,9 @@ async function loadPlugins({ pluginConfigs, context, }) {
|
|
|
57
57
|
context,
|
|
58
58
|
});
|
|
59
59
|
// 2. Plugin Lifecycle - loadContent.
|
|
60
|
-
// Currently plugins run lifecycle methods in parallel and are not
|
|
61
|
-
// We could change this in future if there are plugins which
|
|
62
|
-
// run in certain order or depend on others for data.
|
|
60
|
+
// Currently plugins run lifecycle methods in parallel and are not
|
|
61
|
+
// order-dependent. We could change this in future if there are plugins which
|
|
62
|
+
// need to run in certain order or depend on others for data.
|
|
63
63
|
const loadedPlugins = await Promise.all(plugins.map(async (plugin) => {
|
|
64
64
|
const content = plugin.loadContent ? await plugin.loadContent() : null;
|
|
65
65
|
return { ...plugin, content };
|
|
@@ -134,9 +134,9 @@ async function loadPlugins({ pluginConfigs, context, }) {
|
|
|
134
134
|
});
|
|
135
135
|
}));
|
|
136
136
|
// 4. Plugin Lifecycle - routesLoaded.
|
|
137
|
-
// Currently plugins run lifecycle methods in parallel and are not
|
|
138
|
-
// We could change this in future if there are plugins which
|
|
139
|
-
// run in certain order or depend on others for data.
|
|
137
|
+
// Currently plugins run lifecycle methods in parallel and are not
|
|
138
|
+
// order-dependent. We could change this in future if there are plugins which
|
|
139
|
+
// need to run in certain order or depend on others for data.
|
|
140
140
|
await Promise.all(contentLoadedTranslatedPlugins.map(async (plugin) => {
|
|
141
141
|
if (!plugin.routesLoaded) {
|
|
142
142
|
return null;
|
|
@@ -71,9 +71,7 @@ function getOptionValidationFunction(normalizedPluginConfig) {
|
|
|
71
71
|
// support both commonjs and ES modules
|
|
72
72
|
return ((_c = (_b = (_a = normalizedPluginConfig.pluginModule.module) === null || _a === void 0 ? void 0 : _a.default) === null || _b === void 0 ? void 0 : _b.validateOptions) !== null && _c !== void 0 ? _c : (_d = normalizedPluginConfig.pluginModule.module) === null || _d === void 0 ? void 0 : _d.validateOptions);
|
|
73
73
|
}
|
|
74
|
-
|
|
75
|
-
return normalizedPluginConfig.plugin.validateOptions;
|
|
76
|
-
}
|
|
74
|
+
return normalizedPluginConfig.plugin.validateOptions;
|
|
77
75
|
}
|
|
78
76
|
function getThemeValidationFunction(normalizedPluginConfig) {
|
|
79
77
|
var _a, _b;
|
|
@@ -81,13 +79,11 @@ function getThemeValidationFunction(normalizedPluginConfig) {
|
|
|
81
79
|
// support both commonjs and ES modules
|
|
82
80
|
return ((_b = (_a = normalizedPluginConfig.pluginModule.module.default) === null || _a === void 0 ? void 0 : _a.validateThemeConfig) !== null && _b !== void 0 ? _b : normalizedPluginConfig.pluginModule.module.validateThemeConfig);
|
|
83
81
|
}
|
|
84
|
-
|
|
85
|
-
return normalizedPluginConfig.plugin.validateThemeConfig;
|
|
86
|
-
}
|
|
82
|
+
return normalizedPluginConfig.plugin.validateThemeConfig;
|
|
87
83
|
}
|
|
88
84
|
async function initPlugins({ pluginConfigs, context, }) {
|
|
89
|
-
// We need to resolve plugins from the perspective of the siteDir, since the
|
|
90
|
-
// declares the dependency on these plugins.
|
|
85
|
+
// We need to resolve plugins from the perspective of the siteDir, since the
|
|
86
|
+
// siteDir's package.json declares the dependency on these plugins.
|
|
91
87
|
const pluginRequire = (0, module_1.createRequire)(context.siteConfigPath);
|
|
92
88
|
function doGetPluginVersion(normalizedPluginConfig) {
|
|
93
89
|
var _a, _b;
|
|
@@ -96,9 +92,7 @@ async function initPlugins({ pluginConfigs, context, }) {
|
|
|
96
92
|
const pluginPath = pluginRequire.resolve((_b = normalizedPluginConfig.pluginModule) === null || _b === void 0 ? void 0 : _b.path);
|
|
97
93
|
return (0, versions_1.getPluginVersion)(pluginPath, context.siteDir);
|
|
98
94
|
}
|
|
99
|
-
|
|
100
|
-
return { type: 'local' };
|
|
101
|
-
}
|
|
95
|
+
return { type: 'local' };
|
|
102
96
|
}
|
|
103
97
|
function doValidateThemeConfig(normalizedPluginConfig) {
|
|
104
98
|
const validateThemeConfig = getThemeValidationFunction(normalizedPluginConfig);
|
|
@@ -108,9 +102,7 @@ async function initPlugins({ pluginConfigs, context, }) {
|
|
|
108
102
|
themeConfig: context.siteConfig.themeConfig,
|
|
109
103
|
});
|
|
110
104
|
}
|
|
111
|
-
|
|
112
|
-
return context.siteConfig.themeConfig;
|
|
113
|
-
}
|
|
105
|
+
return context.siteConfig.themeConfig;
|
|
114
106
|
}
|
|
115
107
|
function doValidatePluginOptions(normalizedPluginConfig) {
|
|
116
108
|
var _a;
|
|
@@ -121,14 +113,12 @@ async function initPlugins({ pluginConfigs, context, }) {
|
|
|
121
113
|
options: normalizedPluginConfig.options,
|
|
122
114
|
});
|
|
123
115
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
};
|
|
131
|
-
}
|
|
116
|
+
// Important to ensure all plugins have an id
|
|
117
|
+
// as we don't go through the Joi schema that adds it
|
|
118
|
+
return {
|
|
119
|
+
...normalizedPluginConfig.options,
|
|
120
|
+
id: (_a = normalizedPluginConfig.options.id) !== null && _a !== void 0 ? _a : utils_1.DEFAULT_PLUGIN_ID,
|
|
121
|
+
};
|
|
132
122
|
}
|
|
133
123
|
async function initializePlugin(pluginConfig) {
|
|
134
124
|
const normalizedPluginConfig = normalizePluginConfig(pluginConfig, pluginRequire);
|
|
@@ -11,8 +11,8 @@ const module_1 = require("module");
|
|
|
11
11
|
const import_fresh_1 = (0, tslib_1.__importDefault)(require("import-fresh"));
|
|
12
12
|
const moduleShorthand_1 = require("../moduleShorthand");
|
|
13
13
|
function loadPresets(context) {
|
|
14
|
-
// We need to resolve presets from the perspective of the siteDir, since the
|
|
15
|
-
// declares the dependency on these presets.
|
|
14
|
+
// We need to resolve presets from the perspective of the siteDir, since the
|
|
15
|
+
// siteDir's package.json declares the dependency on these presets.
|
|
16
16
|
const presetRequire = (0, module_1.createRequire)(context.siteConfigPath);
|
|
17
17
|
const presets = context.siteConfig.presets || [];
|
|
18
18
|
const unflatPlugins = [];
|
package/lib/server/routes.js
CHANGED
|
@@ -73,7 +73,8 @@ async function loadRoutes(pluginsRouteConfigs, baseUrl) {
|
|
|
73
73
|
}
|
|
74
74
|
// Collect all page paths for injecting it later in the plugin lifecycle
|
|
75
75
|
// This is useful for plugins like sitemaps, redirects etc...
|
|
76
|
-
// If a route has subroutes, it is not necessarily a valid page path (more
|
|
76
|
+
// If a route has subroutes, it is not necessarily a valid page path (more
|
|
77
|
+
// likely to be a wrapper)
|
|
77
78
|
if (!subroutes) {
|
|
78
79
|
routesPaths.push(routePath);
|
|
79
80
|
}
|
|
@@ -17,8 +17,9 @@ function loadThemeAliases(themePaths, userThemePaths) {
|
|
|
17
17
|
themePaths.forEach((themePath) => {
|
|
18
18
|
const themeAliases = (0, alias_1.default)(themePath, true);
|
|
19
19
|
Object.keys(themeAliases).forEach((aliasKey) => {
|
|
20
|
-
// If this alias shadows a previous one, use @theme-init to preserve the
|
|
21
|
-
// @theme-init is only applied once: to the initial theme
|
|
20
|
+
// If this alias shadows a previous one, use @theme-init to preserve the
|
|
21
|
+
// initial one. @theme-init is only applied once: to the initial theme
|
|
22
|
+
// that provided this component
|
|
22
23
|
if (aliasKey in aliases) {
|
|
23
24
|
const componentName = aliasKey.substring(aliasKey.indexOf('/') + 1);
|
|
24
25
|
const initAlias = `@theme-init/${componentName}`;
|
|
@@ -56,7 +56,7 @@ function mergeTranslationFileContent({ existingContent = {}, newContent, options
|
|
|
56
56
|
Object.entries(newContentTransformed).forEach(([key, { message, description }]) => {
|
|
57
57
|
var _a, _b;
|
|
58
58
|
result[key] = {
|
|
59
|
-
// If
|
|
59
|
+
// If messages already exist, we don't override them (unless requested)
|
|
60
60
|
message: options.override
|
|
61
61
|
? message
|
|
62
62
|
: (_b = (_a = existingContent[key]) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : message,
|
|
@@ -162,9 +162,7 @@ async function localizePluginTranslationFile({ siteDir, plugin, locale, translat
|
|
|
162
162
|
},
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
|
-
|
|
166
|
-
return translationFile;
|
|
167
|
-
}
|
|
165
|
+
return translationFile;
|
|
168
166
|
}
|
|
169
167
|
exports.localizePluginTranslationFile = localizePluginTranslationFile;
|
|
170
168
|
async function getPluginsDefaultCodeTranslationMessages(plugins) {
|
|
@@ -36,8 +36,9 @@ function getSiteSourceCodeFilePaths(siteDir) {
|
|
|
36
36
|
function getPluginSourceCodeFilePaths(plugin) {
|
|
37
37
|
var _a, _b, _c;
|
|
38
38
|
// The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths
|
|
39
|
-
// We can use this method as well to know which folders we should try to
|
|
40
|
-
// Hacky/implicit, but do we want to introduce a
|
|
39
|
+
// We can use this method as well to know which folders we should try to
|
|
40
|
+
// extract translations from. Hacky/implicit, but do we want to introduce a
|
|
41
|
+
// new lifecycle method just for that???
|
|
41
42
|
const codePaths = (_b = (_a = plugin.getPathsToWatch) === null || _a === void 0 ? void 0 : _a.call(plugin)) !== null && _b !== void 0 ? _b : [];
|
|
42
43
|
// We also include theme code
|
|
43
44
|
const themePath = (_c = plugin.getThemePath) === null || _c === void 0 ? void 0 : _c.call(plugin);
|
|
@@ -54,14 +55,16 @@ exports.globSourceCodeFilePaths = globSourceCodeFilePaths;
|
|
|
54
55
|
async function getSourceCodeFilePaths(siteDir, plugins) {
|
|
55
56
|
const sitePaths = getSiteSourceCodeFilePaths(siteDir);
|
|
56
57
|
// The getPathsToWatch() generally returns the js/jsx/ts/tsx/md/mdx file paths
|
|
57
|
-
// We can use this method as well to know which folders we should try to
|
|
58
|
-
// Hacky/implicit, but do we want to introduce a
|
|
58
|
+
// We can use this method as well to know which folders we should try to
|
|
59
|
+
// extract translations from. Hacky/implicit, but do we want to introduce a
|
|
60
|
+
// new lifecycle method for that???
|
|
59
61
|
const pluginsPaths = plugins.flatMap(getPluginSourceCodeFilePaths);
|
|
60
62
|
const allPaths = [...sitePaths, ...pluginsPaths];
|
|
61
63
|
return globSourceCodeFilePaths(allPaths);
|
|
62
64
|
}
|
|
63
65
|
async function extractSiteSourceCodeTranslations(siteDir, plugins, babelOptions, extraSourceCodeFilePaths = []) {
|
|
64
|
-
// Should we warn here if the same translation "key" is found in multiple
|
|
66
|
+
// Should we warn here if the same translation "key" is found in multiple
|
|
67
|
+
// source code files?
|
|
65
68
|
function toTranslationFileContent(sourceCodeFileTranslations) {
|
|
66
69
|
return sourceCodeFileTranslations.reduce((acc, item) => ({ ...acc, ...item.translations }), {});
|
|
67
70
|
}
|
|
@@ -92,8 +95,9 @@ async function extractSourceCodeFileTranslations(sourceCodeFilePath, babelOption
|
|
|
92
95
|
const ast = (0, core_1.parse)(code, {
|
|
93
96
|
...babelOptions,
|
|
94
97
|
ast: true,
|
|
95
|
-
// filename is important, because babel does not process the same files
|
|
96
|
-
//
|
|
98
|
+
// filename is important, because babel does not process the same files
|
|
99
|
+
// according to their js/ts extensions.
|
|
100
|
+
// See https://twitter.com/NicoloRibaudo/status/1321130735605002243
|
|
97
101
|
filename: sourceCodeFilePath,
|
|
98
102
|
});
|
|
99
103
|
const translations = await extractSourceCodeAstTranslations(ast, sourceCodeFilePath);
|
|
@@ -169,12 +173,10 @@ Full code: ${(0, generator_1.default)(node).code}`;
|
|
|
169
173
|
typeof attributeValueEvaluated.value === 'string') {
|
|
170
174
|
return attributeValueEvaluated.value;
|
|
171
175
|
}
|
|
172
|
-
|
|
173
|
-
warnings.push(`<Translate> prop=${propName} should be a statically evaluable object.
|
|
176
|
+
warnings.push(`<Translate> prop=${propName} should be a statically evaluable object.
|
|
174
177
|
Example: <Translate id="optional id" description="optional description">Message</Translate>
|
|
175
178
|
Dynamically constructed values are not allowed, because they prevent translations to be extracted.
|
|
176
179
|
${sourceWarningPart(path.node)}`);
|
|
177
|
-
}
|
|
178
180
|
}
|
|
179
181
|
return undefined;
|
|
180
182
|
}
|
|
@@ -191,7 +193,7 @@ ${sourceWarningPart(path.node)}`);
|
|
|
191
193
|
}
|
|
192
194
|
else {
|
|
193
195
|
translations[id] = {
|
|
194
|
-
message:
|
|
196
|
+
message: id,
|
|
195
197
|
...(description && { description }),
|
|
196
198
|
};
|
|
197
199
|
}
|
|
@@ -199,8 +201,9 @@ ${sourceWarningPart(path.node)}`);
|
|
|
199
201
|
}
|
|
200
202
|
// Handle single non-empty content
|
|
201
203
|
const singleChildren = childrenPath
|
|
202
|
-
// Remove empty/useless text nodes that might be around our
|
|
203
|
-
// Makes the translation system more reliable to JSX
|
|
204
|
+
// Remove empty/useless text nodes that might be around our
|
|
205
|
+
// translation! Makes the translation system more reliable to JSX
|
|
206
|
+
// formatting issues
|
|
204
207
|
.filter((children) => !(children.isJSXText() &&
|
|
205
208
|
children.node.value.replace('\n', '').trim() === ''))
|
|
206
209
|
.pop();
|
|
@@ -231,7 +234,7 @@ ${sourceWarningPart(path.node)}`);
|
|
|
231
234
|
const args = path.get('arguments');
|
|
232
235
|
if (args.length === 1 || args.length === 2) {
|
|
233
236
|
const firstArgPath = args[0];
|
|
234
|
-
//
|
|
237
|
+
// translate("x" + "y"); => translate("xy");
|
|
235
238
|
const firstArgEvaluated = firstArgPath.evaluate();
|
|
236
239
|
if (firstArgEvaluated.confident &&
|
|
237
240
|
typeof firstArgEvaluated.value === 'object') {
|
|
@@ -33,7 +33,8 @@ function getPluginVersion(pluginPath, siteDir) {
|
|
|
33
33
|
const packageJsonPath = (0, path_1.join)(potentialPluginPackageJsonDirectory, 'package.json');
|
|
34
34
|
if ((0, fs_extra_1.existsSync)(packageJsonPath) && (0, fs_extra_1.lstatSync)(packageJsonPath).isFile()) {
|
|
35
35
|
if (potentialPluginPackageJsonDirectory === siteDir) {
|
|
36
|
-
// If the plugin belongs to the same docusaurus project, we classify it
|
|
36
|
+
// If the plugin belongs to the same docusaurus project, we classify it
|
|
37
|
+
// as local plugin.
|
|
37
38
|
return { type: 'project' };
|
|
38
39
|
}
|
|
39
40
|
return {
|
|
@@ -44,7 +45,8 @@ function getPluginVersion(pluginPath, siteDir) {
|
|
|
44
45
|
}
|
|
45
46
|
potentialPluginPackageJsonDirectory = (0, path_1.dirname)(potentialPluginPackageJsonDirectory);
|
|
46
47
|
}
|
|
47
|
-
// In
|
|
48
|
+
// In the case where a plugin is a path where no parent directory contains
|
|
49
|
+
// package.json (e.g. inline plugin), we can only classify it as local.
|
|
48
50
|
return { type: 'local' };
|
|
49
51
|
}
|
|
50
52
|
exports.getPluginVersion = getPluginVersion;
|
package/lib/webpack/base.js
CHANGED
|
@@ -69,16 +69,19 @@ function createBaseConfig(props, isServer, minify = true) {
|
|
|
69
69
|
// When version string changes, cache is evicted
|
|
70
70
|
version: [
|
|
71
71
|
siteMetadata.docusaurusVersion,
|
|
72
|
-
// Webpack does not evict the cache correctly on alias/swizzle change,
|
|
72
|
+
// Webpack does not evict the cache correctly on alias/swizzle change,
|
|
73
|
+
// so we force eviction.
|
|
73
74
|
// See https://github.com/webpack/webpack/issues/13627
|
|
74
75
|
(0, utils_2.md5Hash)(JSON.stringify(themeAliases)),
|
|
75
76
|
].join('-'),
|
|
76
|
-
// When one of those modules/dependencies change (including transitive
|
|
77
|
+
// When one of those modules/dependencies change (including transitive
|
|
78
|
+
// deps), cache is invalidated
|
|
77
79
|
buildDependencies: {
|
|
78
80
|
config: [
|
|
79
81
|
__filename,
|
|
80
82
|
path_1.default.join(__dirname, isServer ? 'server.js' : 'client.js'),
|
|
81
|
-
// Docusaurus config changes can affect MDX/JSX compilation, so we'd
|
|
83
|
+
// Docusaurus config changes can affect MDX/JSX compilation, so we'd
|
|
84
|
+
// rather evict the cache.
|
|
82
85
|
// See https://github.com/questdb/questdb.io/issues/493
|
|
83
86
|
siteConfigPath,
|
|
84
87
|
],
|
|
@@ -120,10 +123,11 @@ function createBaseConfig(props, isServer, minify = true) {
|
|
|
120
123
|
...getDocusaurusAliases(),
|
|
121
124
|
...themeAliases,
|
|
122
125
|
},
|
|
123
|
-
// This allows you to set a fallback for where Webpack should look for
|
|
124
|
-
// We want `@docusaurus/core` own dependencies/`node_modules` to
|
|
125
|
-
// Example: if there is core-js@3 in user's
|
|
126
|
-
// core-js@2, we should use
|
|
126
|
+
// This allows you to set a fallback for where Webpack should look for
|
|
127
|
+
// modules. We want `@docusaurus/core` own dependencies/`node_modules` to
|
|
128
|
+
// "win" if there is conflict. Example: if there is core-js@3 in user's
|
|
129
|
+
// own node_modules, but core depends on core-js@2, we should use
|
|
130
|
+
// core-js@2.
|
|
127
131
|
modules: [
|
|
128
132
|
path_1.default.resolve(__dirname, '..', '..', 'node_modules'),
|
|
129
133
|
'node_modules',
|
|
@@ -135,7 +139,8 @@ function createBaseConfig(props, isServer, minify = true) {
|
|
|
135
139
|
},
|
|
136
140
|
optimization: {
|
|
137
141
|
removeAvailableModules: false,
|
|
138
|
-
// Only minimize client bundle in production because server bundle is only
|
|
142
|
+
// Only minimize client bundle in production because server bundle is only
|
|
143
|
+
// used for static site generation
|
|
139
144
|
minimize: minimizeEnabled,
|
|
140
145
|
minimizer: minimizeEnabled
|
|
141
146
|
? (0, utils_1.getMinimizer)(useSimpleCssMinifier)
|
|
@@ -143,7 +148,9 @@ function createBaseConfig(props, isServer, minify = true) {
|
|
|
143
148
|
splitChunks: isServer
|
|
144
149
|
? false
|
|
145
150
|
: {
|
|
146
|
-
// Since the chunk name includes all origin chunk names it's
|
|
151
|
+
// Since the chunk name includes all origin chunk names it's
|
|
152
|
+
// recommended for production builds with long term caching to NOT
|
|
153
|
+
// include [name] in the filenames
|
|
147
154
|
name: false,
|
|
148
155
|
cacheGroups: {
|
|
149
156
|
// disable the built-in cacheGroups
|
|
@@ -217,8 +224,9 @@ function createBaseConfig(props, isServer, minify = true) {
|
|
|
217
224
|
chunkFilename: isProd
|
|
218
225
|
? 'assets/css/[name].[contenthash:8].css'
|
|
219
226
|
: '[name].css',
|
|
220
|
-
// remove css order warnings if css imports are not sorted
|
|
221
|
-
//
|
|
227
|
+
// remove css order warnings if css imports are not sorted
|
|
228
|
+
// alphabetically. See https://github.com/webpack-contrib/mini-css-extract-plugin/pull/422
|
|
229
|
+
// for more reasoning
|
|
222
230
|
ignoreOrder: true,
|
|
223
231
|
}),
|
|
224
232
|
],
|
package/lib/webpack/client.js
CHANGED
|
@@ -18,7 +18,9 @@ function createClientConfig(props, minify = true) {
|
|
|
18
18
|
const isBuilding = process.argv[2] === 'build';
|
|
19
19
|
const config = (0, base_1.createBaseConfig)(props, false, minify);
|
|
20
20
|
const clientConfig = (0, webpack_merge_1.default)(config, {
|
|
21
|
-
//
|
|
21
|
+
// useless, disabled on purpose (errors on existing sites with no
|
|
22
|
+
// browserslist config)
|
|
23
|
+
// target: 'browserslist',
|
|
22
24
|
entry: path_1.default.resolve(__dirname, '../client/clientEntry.js'),
|
|
23
25
|
optimization: {
|
|
24
26
|
// Keep the runtime chunk separated to enable long term caching
|
|
@@ -33,7 +35,8 @@ function createClientConfig(props, minify = true) {
|
|
|
33
35
|
}),
|
|
34
36
|
],
|
|
35
37
|
});
|
|
36
|
-
// When building include the plugin to force terminate building if errors
|
|
38
|
+
// When building, include the plugin to force terminate building if errors
|
|
39
|
+
// happened in the client bundle.
|
|
37
40
|
if (isBuilding) {
|
|
38
41
|
(_a = clientConfig.plugins) === null || _a === void 0 ? void 0 : _a.push({
|
|
39
42
|
apply: (compiler) => {
|
|
@@ -5,6 +5,17 @@
|
|
|
5
5
|
* LICENSE file in the root directory of this source tree.
|
|
6
6
|
*/
|
|
7
7
|
import { type Compiler } from 'webpack';
|
|
8
|
+
/**
|
|
9
|
+
* We modify webpack runtime to add an extra function called
|
|
10
|
+
* "__webpack_require__.gca" that will allow us to get the corresponding chunk
|
|
11
|
+
* asset for a webpack chunk. Pass it the chunkName or chunkId you want to load.
|
|
12
|
+
* For example: if you have a chunk named "my-chunk-name" that will map to
|
|
13
|
+
* "/publicPath/0a84b5e7.c8e35c7a.js" as its corresponding output path
|
|
14
|
+
* __webpack_require__.gca("my-chunk-name") will return
|
|
15
|
+
* "/publicPath/0a84b5e7.c8e35c7a.js"
|
|
16
|
+
*
|
|
17
|
+
* "gca" stands for "get chunk asset"
|
|
18
|
+
*/
|
|
8
19
|
declare class ChunkAssetPlugin {
|
|
9
20
|
apply(compiler: Compiler): void;
|
|
10
21
|
}
|
|
@@ -8,16 +8,20 @@
|
|
|
8
8
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
9
|
const webpack_1 = require("webpack");
|
|
10
10
|
const pluginName = 'chunk-asset-plugin';
|
|
11
|
+
/**
|
|
12
|
+
* We modify webpack runtime to add an extra function called
|
|
13
|
+
* "__webpack_require__.gca" that will allow us to get the corresponding chunk
|
|
14
|
+
* asset for a webpack chunk. Pass it the chunkName or chunkId you want to load.
|
|
15
|
+
* For example: if you have a chunk named "my-chunk-name" that will map to
|
|
16
|
+
* "/publicPath/0a84b5e7.c8e35c7a.js" as its corresponding output path
|
|
17
|
+
* __webpack_require__.gca("my-chunk-name") will return
|
|
18
|
+
* "/publicPath/0a84b5e7.c8e35c7a.js"
|
|
19
|
+
*
|
|
20
|
+
* "gca" stands for "get chunk asset"
|
|
21
|
+
*/
|
|
11
22
|
class ChunkAssetPlugin {
|
|
12
23
|
apply(compiler) {
|
|
13
24
|
compiler.hooks.thisCompilation.tap(pluginName, ({ mainTemplate }) => {
|
|
14
|
-
/* We modify webpack runtime to add an extra function called "__webpack_require__.gca"
|
|
15
|
-
that will allow us to get the corresponding chunk asset for a webpack chunk.
|
|
16
|
-
Pass it the chunkName or chunkId you want to load.
|
|
17
|
-
For example: if you have a chunk named "my-chunk-name" that will map to "/publicPath/0a84b5e7.c8e35c7a.js" as its corresponding output path
|
|
18
|
-
__webpack_require__.gca("my-chunk-name") will return "/publicPath/0a84b5e7.c8e35c7a.js"
|
|
19
|
-
"gca" stands for "get chunk asset"
|
|
20
|
-
*/
|
|
21
25
|
mainTemplate.hooks.requireExtensions.tap(pluginName, (source, chunk) => {
|
|
22
26
|
const chunkIdToName = chunk.getChunkMaps(false).name;
|
|
23
27
|
const chunkNameToId = Object.create(null);
|
|
@@ -30,10 +34,13 @@ class ChunkAssetPlugin {
|
|
|
30
34
|
buf.push(
|
|
31
35
|
// If chunkName is passed, we convert it to chunk asset url
|
|
32
36
|
// .p => public path url ("/" or "/baseUrl/")
|
|
33
|
-
// .u(chunkId) =>
|
|
34
|
-
//
|
|
37
|
+
// .u(chunkId) =>
|
|
38
|
+
// chunk asset url ("assets/js/x63b64xd.contentHash.js")
|
|
39
|
+
// not sure where this is documented, but this link was helpful:
|
|
40
|
+
// https://programmer.help/blogs/5d68849083e1a.html
|
|
35
41
|
//
|
|
36
|
-
// Note: __webpack_require__.gca() is called in docusaurus.ts for
|
|
42
|
+
// Note: __webpack_require__.gca() is called in docusaurus.ts for
|
|
43
|
+
// prefetching
|
|
37
44
|
// Note: we previously used jsonpScriptSrc (Webpack 4)
|
|
38
45
|
`__webpack_require__.gca = function(chunkId) { chunkId = ${JSON.stringify(chunkNameToId)}[chunkId]||chunkId; return __webpack_require__.p + __webpack_require__.u(chunkId); };`);
|
|
39
46
|
return webpack_1.Template.asString(buf);
|
|
@@ -13,8 +13,8 @@
|
|
|
13
13
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
14
|
* copies of the Software, and to permit persons to whom the Software is
|
|
15
15
|
* furnished to do so, subject to the following conditions:
|
|
16
|
-
* The above copyright notice and this permission notice shall be included in
|
|
17
|
-
* copies or substantial portions of the Software.
|
|
16
|
+
* The above copyright notice and this permission notice shall be included in
|
|
17
|
+
* all copies or substantial portions of the Software.
|
|
18
18
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
19
19
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
20
20
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
@@ -69,7 +69,8 @@ declare class CleanWebpackPlugin {
|
|
|
69
69
|
*
|
|
70
70
|
* Only happens once.
|
|
71
71
|
*
|
|
72
|
-
* Warning: It is recommended to initially clean your build directory outside
|
|
72
|
+
* Warning: It is recommended to initially clean your build directory outside
|
|
73
|
+
* of webpack to minimize unexpected behavior.
|
|
73
74
|
*/
|
|
74
75
|
handleInitial(): void;
|
|
75
76
|
handleDone(stats: Stats): void;
|
|
@@ -60,7 +60,8 @@ class CleanWebpackPlugin {
|
|
|
60
60
|
*
|
|
61
61
|
* Only happens once.
|
|
62
62
|
*
|
|
63
|
-
* Warning: It is recommended to initially clean your build directory outside
|
|
63
|
+
* Warning: It is recommended to initially clean your build directory outside
|
|
64
|
+
* of webpack to minimize unexpected behavior.
|
|
64
65
|
*/
|
|
65
66
|
handleInitial() {
|
|
66
67
|
if (this.initialClean) {
|
package/lib/webpack/server.js
CHANGED
|
@@ -57,9 +57,10 @@ function createServerConfig({ props, onLinksCollected = () => { }, }) {
|
|
|
57
57
|
},
|
|
58
58
|
paths: ssgPaths,
|
|
59
59
|
preferFoldersOutput: trailingSlash,
|
|
60
|
-
// When using "new URL('file.js', import.meta.url)", Webpack will emit
|
|
61
|
-
//
|
|
62
|
-
//
|
|
60
|
+
// When using "new URL('file.js', import.meta.url)", Webpack will emit
|
|
61
|
+
// __filename, and this plugin will throw. not sure the __filename value
|
|
62
|
+
// has any importance for this plugin, just using an empty string to
|
|
63
|
+
// avoid the error. See https://github.com/facebook/docusaurus/issues/4922
|
|
63
64
|
globals: { __filename: '' },
|
|
64
65
|
}),
|
|
65
66
|
// Show compilation progress bar.
|
package/lib/webpack/utils.js
CHANGED
|
@@ -95,13 +95,12 @@ function getBabelOptions({ isServer, babelOptions, } = {}) {
|
|
|
95
95
|
caller: { name: isServer ? 'server' : 'client' },
|
|
96
96
|
};
|
|
97
97
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
}
|
|
98
|
+
return {
|
|
99
|
+
...(babelOptions !== null && babelOptions !== void 0 ? babelOptions : { presets: [require.resolve('../babel/preset')] }),
|
|
100
|
+
babelrc: false,
|
|
101
|
+
configFile: false,
|
|
102
|
+
caller: { name: isServer ? 'server' : 'client' },
|
|
103
|
+
};
|
|
105
104
|
}
|
|
106
105
|
exports.getBabelOptions = getBabelOptions;
|
|
107
106
|
// Name is generic on purpose
|
|
@@ -308,8 +307,8 @@ function getMinimizer(useSimpleCssMinifier = false) {
|
|
|
308
307
|
output: {
|
|
309
308
|
ecma: 5,
|
|
310
309
|
comments: false,
|
|
311
|
-
// Turned on because emoji and regex is not minified properly using
|
|
312
|
-
// https://github.com/facebook/create-react-app/issues/2488
|
|
310
|
+
// Turned on because emoji and regex is not minified properly using
|
|
311
|
+
// default. See https://github.com/facebook/create-react-app/issues/2488
|
|
313
312
|
ascii_only: true,
|
|
314
313
|
},
|
|
315
314
|
},
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@docusaurus/core",
|
|
3
3
|
"description": "Easy to Maintain Open Source Documentation Websites",
|
|
4
|
-
"version": "0.0.0-
|
|
4
|
+
"version": "0.0.0-4524",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"publishConfig": {
|
|
7
7
|
"access": "public"
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"@babel/runtime": "^7.16.3",
|
|
42
42
|
"@babel/runtime-corejs3": "^7.16.3",
|
|
43
43
|
"@babel/traverse": "^7.16.3",
|
|
44
|
-
"@docusaurus/cssnano-preset": "0.0.0-
|
|
45
|
-
"@docusaurus/logger": "0.0.0-
|
|
46
|
-
"@docusaurus/mdx-loader": "0.0.0-
|
|
44
|
+
"@docusaurus/cssnano-preset": "0.0.0-4524",
|
|
45
|
+
"@docusaurus/logger": "0.0.0-4524",
|
|
46
|
+
"@docusaurus/mdx-loader": "0.0.0-4524",
|
|
47
47
|
"@docusaurus/react-loadable": "5.5.2",
|
|
48
|
-
"@docusaurus/utils": "0.0.0-
|
|
49
|
-
"@docusaurus/utils-common": "0.0.0-
|
|
50
|
-
"@docusaurus/utils-validation": "0.0.0-
|
|
48
|
+
"@docusaurus/utils": "0.0.0-4524",
|
|
49
|
+
"@docusaurus/utils-common": "0.0.0-4524",
|
|
50
|
+
"@docusaurus/utils-validation": "0.0.0-4524",
|
|
51
51
|
"@slorber/static-site-generator-webpack-plugin": "^4.0.0",
|
|
52
52
|
"@svgr/webpack": "^6.0.0",
|
|
53
53
|
"autoprefixer": "^10.3.5",
|
|
@@ -105,8 +105,8 @@
|
|
|
105
105
|
"webpackbar": "^5.0.2"
|
|
106
106
|
},
|
|
107
107
|
"devDependencies": {
|
|
108
|
-
"@docusaurus/module-type-aliases": "0.0.0-
|
|
109
|
-
"@docusaurus/types": "0.0.0-
|
|
108
|
+
"@docusaurus/module-type-aliases": "0.0.0-4524",
|
|
109
|
+
"@docusaurus/types": "0.0.0-4524",
|
|
110
110
|
"@types/copy-webpack-plugin": "^8.0.1",
|
|
111
111
|
"@types/detect-port": "^1.3.0",
|
|
112
112
|
"@types/mini-css-extract-plugin": "^1.4.3",
|
|
@@ -129,5 +129,5 @@
|
|
|
129
129
|
"engines": {
|
|
130
130
|
"node": ">=14"
|
|
131
131
|
},
|
|
132
|
-
"gitHead": "
|
|
132
|
+
"gitHead": "41a6bff3a67446f7d2a2972cbe4d05fec7378a15"
|
|
133
133
|
}
|