@docusaurus/bundler 3.5.2 → 3.6.0-canary-6132

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.
Files changed (42) hide show
  1. package/LICENSE +21 -0
  2. package/lib/compiler.d.ts +22 -0
  3. package/lib/compiler.d.ts.map +1 -0
  4. package/lib/compiler.js +66 -0
  5. package/lib/compiler.js.map +1 -0
  6. package/lib/currentBundler.d.ts +33 -0
  7. package/lib/currentBundler.d.ts.map +1 -0
  8. package/lib/currentBundler.js +73 -0
  9. package/lib/currentBundler.js.map +1 -0
  10. package/lib/importFaster.d.ts +17 -0
  11. package/lib/importFaster.d.ts.map +1 -0
  12. package/lib/importFaster.js +57 -0
  13. package/lib/importFaster.js.map +1 -0
  14. package/lib/index.d.ts +6 -22
  15. package/lib/index.d.ts.map +1 -1
  16. package/lib/index.js +18 -52
  17. package/lib/index.js.map +1 -1
  18. package/lib/loaders/jsLoader.d.ts +16 -0
  19. package/lib/loaders/jsLoader.d.ts.map +1 -0
  20. package/lib/loaders/jsLoader.js +67 -0
  21. package/lib/loaders/jsLoader.js.map +1 -0
  22. package/lib/loaders/styleLoader.d.ts +11 -0
  23. package/lib/loaders/styleLoader.d.ts.map +1 -0
  24. package/lib/loaders/styleLoader.js +68 -0
  25. package/lib/loaders/styleLoader.js.map +1 -0
  26. package/lib/minification.d.ts +14 -0
  27. package/lib/minification.d.ts.map +1 -0
  28. package/lib/minification.js +147 -0
  29. package/lib/minification.js.map +1 -0
  30. package/lib/minifyHtml.d.ts +18 -0
  31. package/lib/minifyHtml.d.ts.map +1 -0
  32. package/lib/minifyHtml.js +91 -0
  33. package/lib/minifyHtml.js.map +1 -0
  34. package/package.json +35 -5
  35. package/src/compiler.ts +87 -0
  36. package/src/currentBundler.ts +106 -0
  37. package/src/importFaster.ts +77 -0
  38. package/src/index.ts +17 -82
  39. package/src/loaders/jsLoader.ts +89 -0
  40. package/src/loaders/styleLoader.ts +80 -0
  41. package/src/minification.ts +176 -0
  42. package/src/minifyHtml.ts +111 -0
package/src/index.ts CHANGED
@@ -5,85 +5,20 @@
5
5
  * LICENSE file in the root directory of this source tree.
6
6
  */
7
7
 
8
- import webpack from 'webpack';
9
- import WebpackBar from 'webpackbar';
10
- import MiniCssExtractPlugin from 'mini-css-extract-plugin';
11
- import CopyWebpackPlugin from 'copy-webpack-plugin';
12
- import logger from '@docusaurus/logger';
13
- import type {CurrentBundler, DocusaurusConfig} from '@docusaurus/types';
14
-
15
- // We inject a site config slice because the Rspack flag might change place
16
- type SiteConfigSlice = {
17
- future: {
18
- experimental_faster: Pick<
19
- DocusaurusConfig['future']['experimental_faster'],
20
- 'rspackBundler'
21
- >;
22
- };
23
- };
24
-
25
- function isRspack(siteConfig: SiteConfigSlice): boolean {
26
- return siteConfig.future.experimental_faster.rspackBundler;
27
- }
28
-
29
- export async function getCurrentBundler({
30
- siteConfig,
31
- }: {
32
- siteConfig: SiteConfigSlice;
33
- }): Promise<CurrentBundler> {
34
- if (isRspack(siteConfig)) {
35
- // TODO add support for Rspack
36
- logger.error(
37
- 'Rspack bundler is not supported yet, will use Webpack instead',
38
- );
39
- }
40
- return {
41
- name: 'webpack',
42
- instance: webpack,
43
- };
44
- }
45
-
46
- export async function getCSSExtractPlugin({
47
- currentBundler,
48
- }: {
49
- currentBundler: CurrentBundler;
50
- }): Promise<typeof MiniCssExtractPlugin> {
51
- if (currentBundler.name === 'rspack') {
52
- throw new Error('Rspack bundler is not supported yet');
53
- }
54
- return MiniCssExtractPlugin;
55
- }
56
-
57
- export async function getCopyPlugin({
58
- currentBundler,
59
- }: {
60
- currentBundler: CurrentBundler;
61
- }): Promise<typeof CopyWebpackPlugin> {
62
- if (currentBundler.name === 'rspack') {
63
- throw new Error('Rspack bundler is not supported yet');
64
- }
65
- // https://github.com/webpack-contrib/copy-webpack-plugin
66
- return CopyWebpackPlugin;
67
- }
68
-
69
- export async function getProgressBarPlugin({
70
- currentBundler,
71
- }: {
72
- currentBundler: CurrentBundler;
73
- }): Promise<typeof WebpackBar> {
74
- if (currentBundler.name === 'rspack') {
75
- class CustomRspackProgressPlugin extends currentBundler.instance
76
- .ProgressPlugin {
77
- constructor({name}: {name: string}) {
78
- // TODO add support for color
79
- // Unfortunately the rspack.ProgressPlugin does not have a name option
80
- // See https://rspack.dev/plugins/webpack/progress-plugin
81
- // @ts-expect-error: adapt Rspack ProgressPlugin constructor
82
- super({prefix: name});
83
- }
84
- }
85
- return CustomRspackProgressPlugin as typeof WebpackBar;
86
- }
87
-
88
- return WebpackBar;
89
- }
8
+ export {printStatsWarnings, formatStatsErrorMessage, compile} from './compiler';
9
+
10
+ export {
11
+ getCurrentBundler,
12
+ getCSSExtractPlugin,
13
+ getCopyPlugin,
14
+ getProgressBarPlugin,
15
+ } from './currentBundler';
16
+
17
+ export {getMinimizers} from './minification';
18
+ export {
19
+ getHtmlMinifier,
20
+ type HtmlMinifier,
21
+ type HtmlMinifierType,
22
+ } from './minifyHtml';
23
+ export {createJsLoaderFactory} from './loaders/jsLoader';
24
+ export {createStyleLoadersFactory} from './loaders/styleLoader';
@@ -0,0 +1,89 @@
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
+
8
+ import {getBabelOptions} from '@docusaurus/babel';
9
+ import {importSwcLoader, importGetSwcLoaderOptions} from '../importFaster';
10
+ import {getCurrentBundler} from '../currentBundler';
11
+ import type {ConfigureWebpackUtils, DocusaurusConfig} from '@docusaurus/types';
12
+
13
+ const BabelJsLoaderFactory: ConfigureWebpackUtils['getJSLoader'] = ({
14
+ isServer,
15
+ babelOptions,
16
+ }) => {
17
+ return {
18
+ loader: require.resolve('babel-loader'),
19
+ options: getBabelOptions({isServer, babelOptions}),
20
+ };
21
+ };
22
+
23
+ async function createSwcJsLoaderFactory(): Promise<
24
+ ConfigureWebpackUtils['getJSLoader']
25
+ > {
26
+ const loader = await importSwcLoader();
27
+ const getOptions = await importGetSwcLoaderOptions();
28
+ return ({isServer}) => {
29
+ return {
30
+ loader,
31
+ options: getOptions({isServer}),
32
+ };
33
+ };
34
+ }
35
+
36
+ // Same as swcLoader, except we use the built-in SWC loader
37
+ async function createRspackSwcJsLoaderFactory(): Promise<
38
+ ConfigureWebpackUtils['getJSLoader']
39
+ > {
40
+ const loader = 'builtin:swc-loader';
41
+ const getOptions = await importGetSwcLoaderOptions();
42
+ return ({isServer}) => {
43
+ return {
44
+ loader,
45
+ options: getOptions({isServer}),
46
+ };
47
+ };
48
+ }
49
+
50
+ // Confusing: function that creates a function that creates actual js loaders
51
+ // This is done on purpose because the js loader factory is a public API
52
+ // It is injected in configureWebpack plugin lifecycle for plugin authors
53
+ export async function createJsLoaderFactory({
54
+ siteConfig,
55
+ }: {
56
+ siteConfig: {
57
+ webpack?: DocusaurusConfig['webpack'];
58
+ future: {
59
+ experimental_faster: DocusaurusConfig['future']['experimental_faster'];
60
+ };
61
+ };
62
+ }): Promise<ConfigureWebpackUtils['getJSLoader']> {
63
+ const currentBundler = await getCurrentBundler({siteConfig});
64
+ const isSWCLoader = siteConfig.future.experimental_faster.swcJsLoader;
65
+ if (currentBundler.name === 'rspack') {
66
+ return isSWCLoader
67
+ ? createRspackSwcJsLoaderFactory()
68
+ : BabelJsLoaderFactory;
69
+ }
70
+ const jsLoader = siteConfig.webpack?.jsLoader ?? 'babel';
71
+ if (
72
+ jsLoader instanceof Function &&
73
+ siteConfig.future?.experimental_faster.swcJsLoader
74
+ ) {
75
+ throw new Error(
76
+ "You can't use a custom webpack.jsLoader and experimental_faster.swcJsLoader at the same time",
77
+ );
78
+ }
79
+ if (jsLoader instanceof Function) {
80
+ return ({isServer}) => jsLoader(isServer);
81
+ }
82
+ if (siteConfig.future?.experimental_faster.swcJsLoader) {
83
+ return createSwcJsLoaderFactory();
84
+ }
85
+ if (jsLoader === 'babel') {
86
+ return BabelJsLoaderFactory;
87
+ }
88
+ throw new Error(`Docusaurus bug: unexpected jsLoader value${jsLoader}`);
89
+ }
@@ -0,0 +1,80 @@
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
+
8
+ import {getCSSExtractPlugin} from '../currentBundler';
9
+ import type {ConfigureWebpackUtils, CurrentBundler} from '@docusaurus/types';
10
+
11
+ export async function createStyleLoadersFactory({
12
+ currentBundler,
13
+ }: {
14
+ currentBundler: CurrentBundler;
15
+ }): Promise<ConfigureWebpackUtils['getStyleLoaders']> {
16
+ const CssExtractPlugin = await getCSSExtractPlugin({currentBundler});
17
+
18
+ return function getStyleLoaders(
19
+ isServer: boolean,
20
+ cssOptionsArg: {
21
+ [key: string]: unknown;
22
+ } = {},
23
+ ) {
24
+ const cssOptions: {[key: string]: unknown} = {
25
+ // TODO turn esModule on later, see https://github.com/facebook/docusaurus/pull/6424
26
+ esModule: false,
27
+ ...cssOptionsArg,
28
+ };
29
+
30
+ // On the server we don't really need to extract/emit CSS
31
+ // We only need to transform CSS module imports to a styles object
32
+ if (isServer) {
33
+ return cssOptions.modules
34
+ ? [
35
+ {
36
+ loader: require.resolve('css-loader'),
37
+ options: cssOptions,
38
+ },
39
+ ]
40
+ : // Ignore regular CSS files
41
+ [{loader: require.resolve('null-loader')}];
42
+ }
43
+
44
+ return [
45
+ {
46
+ loader: CssExtractPlugin.loader,
47
+ options: {
48
+ esModule: true,
49
+ },
50
+ },
51
+ {
52
+ loader: require.resolve('css-loader'),
53
+ options: cssOptions,
54
+ },
55
+
56
+ // TODO apart for configurePostCss(), do we really need this loader?
57
+ // Note: using postcss here looks inefficient/duplicate
58
+ // But in practice, it's not a big deal because css-loader also uses postcss
59
+ // and is able to reuse the parsed AST from postcss-loader
60
+ // See https://github.com/webpack-contrib/css-loader/blob/master/src/index.js#L159
61
+ {
62
+ // Options for PostCSS as we reference these options twice
63
+ // Adds vendor prefixing based on your specified browser support in
64
+ // package.json
65
+ loader: require.resolve('postcss-loader'),
66
+ options: {
67
+ postcssOptions: {
68
+ // Necessary for external CSS imports to work
69
+ // https://github.com/facebook/create-react-app/issues/2677
70
+ ident: 'postcss',
71
+ plugins: [
72
+ // eslint-disable-next-line global-require
73
+ require('autoprefixer'),
74
+ ],
75
+ },
76
+ },
77
+ },
78
+ ];
79
+ };
80
+ }
@@ -0,0 +1,176 @@
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
+
8
+ import TerserPlugin from 'terser-webpack-plugin';
9
+ import CssMinimizerPlugin from 'css-minimizer-webpack-plugin';
10
+ import {
11
+ importSwcJsMinimizerOptions,
12
+ importLightningCssMinimizerOptions,
13
+ importGetBrowserslistQueries,
14
+ } from './importFaster';
15
+ import {getCurrentBundlerAsRspack} from './currentBundler';
16
+ import type {CustomOptions, CssNanoOptions} from 'css-minimizer-webpack-plugin';
17
+ import type {WebpackPluginInstance} from 'webpack';
18
+ import type {CurrentBundler, FasterConfig} from '@docusaurus/types';
19
+
20
+ export type MinimizersConfig = {
21
+ faster: Pick<FasterConfig, 'swcJsMinimizer' | 'lightningCssMinimizer'>;
22
+ currentBundler: CurrentBundler;
23
+ };
24
+
25
+ // See https://github.com/webpack-contrib/terser-webpack-plugin#parallel
26
+ function getTerserParallel() {
27
+ let terserParallel: boolean | number = true;
28
+ if (process.env.TERSER_PARALLEL === 'false') {
29
+ terserParallel = false;
30
+ } else if (
31
+ process.env.TERSER_PARALLEL &&
32
+ parseInt(process.env.TERSER_PARALLEL, 10) > 0
33
+ ) {
34
+ terserParallel = parseInt(process.env.TERSER_PARALLEL, 10);
35
+ }
36
+ return terserParallel;
37
+ }
38
+
39
+ async function getJsMinimizer({
40
+ faster,
41
+ }: MinimizersConfig): Promise<WebpackPluginInstance> {
42
+ if (faster.swcJsMinimizer) {
43
+ const terserOptions = await importSwcJsMinimizerOptions();
44
+ return new TerserPlugin({
45
+ parallel: getTerserParallel(),
46
+ minify: TerserPlugin.swcMinify,
47
+ terserOptions,
48
+ });
49
+ }
50
+
51
+ return new TerserPlugin({
52
+ parallel: getTerserParallel(),
53
+ // See https://terser.org/docs/options/
54
+ terserOptions: {
55
+ parse: {
56
+ // We want uglify-js to parse ecma 8 code. However, we don't want it
57
+ // to apply any minification steps that turns valid ecma 5 code
58
+ // into invalid ecma 5 code. This is why the 'compress' and 'output'
59
+ // sections only apply transformations that are ecma 5 safe
60
+ // https://github.com/facebook/create-react-app/pull/4234
61
+ ecma: 2020,
62
+ },
63
+ compress: {
64
+ ecma: 5,
65
+ },
66
+ mangle: {
67
+ safari10: true,
68
+ },
69
+ output: {
70
+ ecma: 5,
71
+ comments: false,
72
+ // Turned on because emoji and regex is not minified properly using
73
+ // default. See https://github.com/facebook/create-react-app/issues/2488
74
+ ascii_only: true,
75
+ },
76
+ },
77
+ });
78
+ }
79
+
80
+ async function getLightningCssMinimizer(): Promise<WebpackPluginInstance> {
81
+ return new CssMinimizerPlugin({
82
+ minify: CssMinimizerPlugin.lightningCssMinify,
83
+ minimizerOptions: await importLightningCssMinimizerOptions(),
84
+ });
85
+ }
86
+
87
+ async function getCssNanoMinimizer(): Promise<WebpackPluginInstance> {
88
+ // This is an historical env variable to opt-out of the advanced minimizer
89
+ // Sometimes there's a bug in it and people are happy to disable it
90
+ const useSimpleCssMinifier = process.env.USE_SIMPLE_CSS_MINIFIER === 'true';
91
+ if (useSimpleCssMinifier) {
92
+ return new CssMinimizerPlugin();
93
+ }
94
+
95
+ // Using the array syntax to add 2 minimizers
96
+ // see https://github.com/webpack-contrib/css-minimizer-webpack-plugin#array
97
+ return new CssMinimizerPlugin<[CssNanoOptions, CustomOptions]>({
98
+ minimizerOptions: [
99
+ // CssNano options
100
+ {
101
+ preset: require.resolve('@docusaurus/cssnano-preset'),
102
+ },
103
+ // CleanCss options
104
+ {
105
+ inline: false,
106
+ level: {
107
+ 1: {
108
+ all: false,
109
+ removeWhitespace: true,
110
+ },
111
+ 2: {
112
+ all: true,
113
+ restructureRules: true,
114
+ removeUnusedAtRules: false,
115
+ },
116
+ },
117
+ },
118
+ ],
119
+ minify: [
120
+ CssMinimizerPlugin.cssnanoMinify,
121
+ CssMinimizerPlugin.cleanCssMinify,
122
+ ],
123
+ });
124
+ }
125
+
126
+ async function getCssMinimizer(
127
+ params: MinimizersConfig,
128
+ ): Promise<WebpackPluginInstance> {
129
+ return params.faster.lightningCssMinimizer
130
+ ? getLightningCssMinimizer()
131
+ : getCssNanoMinimizer();
132
+ }
133
+
134
+ async function getWebpackMinimizers(
135
+ params: MinimizersConfig,
136
+ ): Promise<WebpackPluginInstance[]> {
137
+ return Promise.all([getJsMinimizer(params), getCssMinimizer(params)]);
138
+ }
139
+
140
+ async function getRspackMinimizers({
141
+ currentBundler,
142
+ }: MinimizersConfig): Promise<WebpackPluginInstance[]> {
143
+ const rspack = getCurrentBundlerAsRspack({currentBundler});
144
+ const getBrowserslistQueries = await importGetBrowserslistQueries();
145
+ const browserslistQueries = getBrowserslistQueries({isServer: false});
146
+ const swcJsMinimizerOptions = await importSwcJsMinimizerOptions();
147
+ return [
148
+ // See https://rspack.dev/plugins/rspack/swc-js-minimizer-rspack-plugin
149
+ // See https://swc.rs/docs/configuration/minification
150
+ new rspack.SwcJsMinimizerRspackPlugin({
151
+ minimizerOptions: {
152
+ minify: true,
153
+ ...swcJsMinimizerOptions,
154
+ },
155
+ }),
156
+ new rspack.LightningCssMinimizerRspackPlugin({
157
+ minimizerOptions: {
158
+ ...(await importLightningCssMinimizerOptions()),
159
+ // Not sure why but Rspack takes browserslist queries directly
160
+ // While LightningCSS targets are normally not browserslist queries
161
+ // We have to override the option to avoid errors
162
+ // See https://rspack.dev/plugins/rspack/lightning-css-minimizer-rspack-plugin#minimizeroptions
163
+ // See https://lightningcss.dev/transpilation.html
164
+ targets: browserslistQueries,
165
+ },
166
+ }),
167
+ ] as unknown as WebpackPluginInstance[];
168
+ }
169
+
170
+ export async function getMinimizers(
171
+ params: MinimizersConfig,
172
+ ): Promise<WebpackPluginInstance[]> {
173
+ return params.currentBundler.name === 'rspack'
174
+ ? getRspackMinimizers(params)
175
+ : getWebpackMinimizers(params);
176
+ }
@@ -0,0 +1,111 @@
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
+
8
+ import {minify as terserHtmlMinifier} from 'html-minifier-terser';
9
+ import {importSwcHtmlMinifier} from './importFaster';
10
+
11
+ // Historical env variable
12
+ const SkipHtmlMinification = process.env.SKIP_HTML_MINIFICATION === 'true';
13
+
14
+ export type HtmlMinifierType = 'swc' | 'terser';
15
+
16
+ export type HtmlMinifierResult = {
17
+ code: string;
18
+ warnings: string[];
19
+ };
20
+
21
+ export type HtmlMinifier = {
22
+ minify: (html: string) => Promise<HtmlMinifierResult>;
23
+ };
24
+
25
+ const NoopMinifier: HtmlMinifier = {
26
+ minify: async (html: string) => ({code: html, warnings: []}),
27
+ };
28
+
29
+ export async function getHtmlMinifier({
30
+ type,
31
+ }: {
32
+ type: HtmlMinifierType;
33
+ }): Promise<HtmlMinifier> {
34
+ if (SkipHtmlMinification) {
35
+ return NoopMinifier;
36
+ }
37
+ if (type === 'swc') {
38
+ return getSwcMinifier();
39
+ } else {
40
+ return getTerserMinifier();
41
+ }
42
+ }
43
+
44
+ // Minify html with https://github.com/DanielRuf/html-minifier-terser
45
+ async function getTerserMinifier(): Promise<HtmlMinifier> {
46
+ return {
47
+ minify: async function minifyHtmlWithTerser(html) {
48
+ try {
49
+ const code = await terserHtmlMinifier(html, {
50
+ removeComments: false,
51
+ removeRedundantAttributes: true,
52
+ removeEmptyAttributes: true,
53
+ removeScriptTypeAttributes: true,
54
+ removeStyleLinkTypeAttributes: true,
55
+ useShortDoctype: true,
56
+ minifyJS: true,
57
+ });
58
+ return {code, warnings: []};
59
+ } catch (err) {
60
+ throw new Error(`HTML minification failed (Terser)`, {
61
+ cause: err as Error,
62
+ });
63
+ }
64
+ },
65
+ };
66
+ }
67
+
68
+ // Minify html with @swc/html
69
+ // Not well-documented but fast!
70
+ // See https://github.com/swc-project/swc/discussions/9616
71
+ async function getSwcMinifier(): Promise<HtmlMinifier> {
72
+ const swcHtmlMinifier = await importSwcHtmlMinifier();
73
+ return {
74
+ minify: async function minifyHtmlWithSwc(html) {
75
+ try {
76
+ const result = await swcHtmlMinifier(Buffer.from(html), {
77
+ // Removing comments can lead to React hydration errors
78
+ // See https://x.com/sebastienlorber/status/1841966927440478577
79
+ removeComments: false,
80
+ // TODO maybe it's fine to only keep <!-- --> React comments?
81
+ preserveComments: [],
82
+
83
+ // Sorting these attributes (class) can lead to React hydration errors
84
+ sortSpaceSeparatedAttributeValues: false,
85
+ sortAttributes: false,
86
+
87
+ removeRedundantAttributes: 'all',
88
+ removeEmptyAttributes: true,
89
+ minifyJs: true,
90
+ minifyJson: true,
91
+ minifyCss: true,
92
+ });
93
+
94
+ const warnings = (result.errors ?? []).map((diagnostic) => {
95
+ return `[HTML minifier diagnostic - ${diagnostic.level}] ${
96
+ diagnostic.message
97
+ } - ${JSON.stringify(diagnostic.span)}`;
98
+ });
99
+
100
+ return {
101
+ code: result.code,
102
+ warnings,
103
+ };
104
+ } catch (err) {
105
+ throw new Error(`HTML minification failed (SWC)`, {
106
+ cause: err as Error,
107
+ });
108
+ }
109
+ },
110
+ };
111
+ }