diff --git a/packages/docusaurus-theme-common/tsconfig.json b/packages/docusaurus-theme-common/tsconfig.json index f5902ba108..8276c0b706 100644 --- a/packages/docusaurus-theme-common/tsconfig.json +++ b/packages/docusaurus-theme-common/tsconfig.json @@ -3,6 +3,7 @@ "compilerOptions": { "incremental": true, "tsBuildInfoFile": "./lib/.tsbuildinfo", + "module": "esnext", "rootDir": "src", "outDir": "lib" } diff --git a/packages/docusaurus-types/src/index.d.ts b/packages/docusaurus-types/src/index.d.ts index 44f196ab07..0ab274f42c 100644 --- a/packages/docusaurus-types/src/index.d.ts +++ b/packages/docusaurus-types/src/index.d.ts @@ -63,6 +63,9 @@ export interface DocusaurusConfig { } )[]; titleDelimiter?: string; + webpack?: { + jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule); + }; } /** diff --git a/packages/docusaurus/src/commands/build.ts b/packages/docusaurus/src/commands/build.ts index 7074f706b6..60ad1c2725 100644 --- a/packages/docusaurus/src/commands/build.ts +++ b/packages/docusaurus/src/commands/build.ts @@ -187,12 +187,14 @@ async function buildLocale({ configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. clientConfig, false, + props.siteConfig.webpack?.jsLoader, ); serverConfig = applyConfigureWebpack( configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. serverConfig, true, + props.siteConfig.webpack?.jsLoader, ); } }); diff --git a/packages/docusaurus/src/commands/start.ts b/packages/docusaurus/src/commands/start.ts index 2f045b48f1..cd025bd6d5 100644 --- a/packages/docusaurus/src/commands/start.ts +++ b/packages/docusaurus/src/commands/start.ts @@ -153,6 +153,7 @@ export default async function start( configureWebpack.bind(plugin), // The plugin lifecycle may reference `this`. config, false, + props.siteConfig.webpack?.jsLoader, ); } }); diff --git a/packages/docusaurus/src/server/configValidation.ts b/packages/docusaurus/src/server/configValidation.ts index 5eaeae0e25..60de610671 100644 --- a/packages/docusaurus/src/server/configValidation.ts +++ b/packages/docusaurus/src/server/configValidation.ts @@ -134,6 +134,11 @@ const ConfigSchema = Joi.object({ tagline: Joi.string().allow(''), titleDelimiter: Joi.string().default('|'), noIndex: Joi.bool().default(false), + webpack: Joi.object({ + jsLoader: Joi.alternatives() + .try(Joi.string().equal('babel'), Joi.function()) + .optional(), + }).optional(), }); // TODO move to @docusaurus/utils-validation diff --git a/packages/docusaurus/src/webpack/__tests__/base.test.ts b/packages/docusaurus/src/webpack/__tests__/base.test.ts index 588848f083..5891d03df4 100644 --- a/packages/docusaurus/src/webpack/__tests__/base.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/base.test.ts @@ -81,6 +81,7 @@ describe('base webpack config', () => { const props = { outDir: '', siteDir: '', + siteConfig: {}, baseUrl: '', generatedFilesDir: '', routesPaths: '', diff --git a/packages/docusaurus/src/webpack/__tests__/utils.test.ts b/packages/docusaurus/src/webpack/__tests__/utils.test.ts index 8244735e36..1dbde42171 100644 --- a/packages/docusaurus/src/webpack/__tests__/utils.test.ts +++ b/packages/docusaurus/src/webpack/__tests__/utils.test.ts @@ -5,10 +5,11 @@ * LICENSE file in the root directory of this source tree. */ -import {validate, Configuration} from 'webpack'; +import {validate, Configuration, RuleSetRule} from 'webpack'; import path from 'path'; import { + getCustomizableJSLoader, applyConfigureWebpack, applyConfigurePostCss, getFileLoaderUtils, @@ -18,6 +19,40 @@ import { ConfigureWebpackFnMergeStrategy, } from '@docusaurus/types'; +describe('customize JS loader', () => { + test('getCustomizableJSLoader defaults to babel loader', () => { + expect(getCustomizableJSLoader()({isServer: true}).loader).toBe( + require.resolve('babel-loader'), + ); + expect(getCustomizableJSLoader()({isServer: false}).loader).toBe( + require.resolve('babel-loader'), + ); + }); + + test('getCustomizableJSLoader accepts loaders with preset', () => { + expect(getCustomizableJSLoader('babel')({isServer: true}).loader).toBe( + require.resolve('babel-loader'), + ); + expect(getCustomizableJSLoader('babel')({isServer: false}).loader).toBe( + require.resolve('babel-loader'), + ); + }); + + test('getCustomizableJSLoader allows customization', () => { + const customJSLoader = (isServer: boolean): RuleSetRule => ({ + loader: 'my-fast-js-loader', + options: String(isServer), + }); + + expect(getCustomizableJSLoader(customJSLoader)({isServer: true})).toEqual( + customJSLoader(true), + ); + expect(getCustomizableJSLoader(customJSLoader)({isServer: false})).toEqual( + customJSLoader(false), + ); + }); +}); + describe('extending generated webpack config', () => { test('direct mutation on generated webpack config object', async () => { // fake generated webpack config diff --git a/packages/docusaurus/src/webpack/base.ts b/packages/docusaurus/src/webpack/base.ts index 66dae51dc7..78b15b2895 100644 --- a/packages/docusaurus/src/webpack/base.ts +++ b/packages/docusaurus/src/webpack/base.ts @@ -11,7 +11,7 @@ import path from 'path'; import {Configuration} from 'webpack'; import {Props} from '@docusaurus/types'; import { - getJSLoader, + getCustomizableJSLoader, getStyleLoaders, getFileLoaderUtils, getCustomBabelConfigFilePath, @@ -73,6 +73,7 @@ export function createBaseConfig( const { outDir, siteDir, + siteConfig, baseUrl, generatedFilesDir, routesPaths, @@ -205,7 +206,7 @@ export function createBaseConfig( test: /\.(j|t)sx?$/, exclude: excludeJS, use: [ - getJSLoader({ + getCustomizableJSLoader(siteConfig.webpack?.jsLoader)({ isServer, babelOptions: getCustomBabelConfigFilePath(siteDir), }), diff --git a/packages/docusaurus/src/webpack/utils.ts b/packages/docusaurus/src/webpack/utils.ts index f9eda08d13..0ecf28f45b 100644 --- a/packages/docusaurus/src/webpack/utils.ts +++ b/packages/docusaurus/src/webpack/utils.ts @@ -137,7 +137,7 @@ export function getBabelOptions({ // Name is generic on purpose // we want to support multiple js loader implementations (babel + esbuild) -export function getJSLoader({ +function getDefaultBabelLoader({ isServer, babelOptions, }: { @@ -150,6 +150,19 @@ export function getJSLoader({ }; } +export const getCustomizableJSLoader = ( + jsLoader: 'babel' | ((isServer: boolean) => RuleSetRule) = 'babel', +) => ({ + isServer, + babelOptions, +}: { + isServer: boolean; + babelOptions?: TransformOptions | string; +}): RuleSetRule => + jsLoader === 'babel' + ? getDefaultBabelLoader({isServer, babelOptions}) + : jsLoader(isServer); + // TODO remove this before end of 2021? const warnBabelLoaderOnce = memoize(function () { console.warn( @@ -163,7 +176,7 @@ const getBabelLoaderDeprecated = function getBabelLoaderDeprecated( babelOptions?: TransformOptions | string, ) { warnBabelLoaderOnce(); - return getJSLoader({isServer, babelOptions}); + return getDefaultBabelLoader({isServer, babelOptions}); }; // TODO remove this before end of 2021 ? @@ -184,17 +197,19 @@ function getCacheLoaderDeprecated() { * @param configureWebpack a webpack config or a function to modify config * @param config initial webpack config * @param isServer indicates if this is a server webpack configuration + * @param jsLoader custom js loader config * @returns final/ modified webpack config */ export function applyConfigureWebpack( configureWebpack: ConfigureWebpackFn, config: Configuration, isServer: boolean, + jsLoader?: 'babel' | ((isServer: boolean) => RuleSetRule), ): Configuration { // Export some utility functions const utils: ConfigureWebpackUtils = { getStyleLoaders, - getJSLoader, + getJSLoader: getCustomizableJSLoader(jsLoader), getBabelLoader: getBabelLoaderDeprecated, getCacheLoader: getCacheLoaderDeprecated, }; diff --git a/website/docusaurus.config.js b/website/docusaurus.config.js index 434506fa07..5d97fae6c7 100644 --- a/website/docusaurus.config.js +++ b/website/docusaurus.config.js @@ -63,6 +63,16 @@ const isVersioningDisabled = !!process.env.DISABLE_VERSIONING || isI18nStaging; : // Production locales ['en', 'fr', 'ko', 'zh-CN'], }, + webpack: { + jsLoader: (isServer) => ({ + loader: require.resolve('esbuild-loader'), + options: { + loader: 'tsx', + format: isServer ? 'cjs' : undefined, + target: isServer ? 'node12' : 'es2017', + }, + }), + }, onBrokenLinks: 'throw', onBrokenMarkdownLinks: 'warn', favicon: 'img/docusaurus.ico', diff --git a/website/package.json b/website/package.json index c2d970fafd..871d525c81 100644 --- a/website/package.json +++ b/website/package.json @@ -39,6 +39,7 @@ "@docusaurus/theme-live-codeblock": "2.0.0-beta.0", "clsx": "^1.1.1", "color": "^3.1.3", + "esbuild-loader": "2.13.0", "netlify-plugin-cache": "^1.0.3", "npm-to-yarn": "^1.0.0-2", "react": "^17.0.1", diff --git a/yarn.lock b/yarn.lock index 4d064e21b1..7b07d7fed3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8060,6 +8060,23 @@ es6-promisify@^5.0.0: dependencies: es6-promise "^4.0.3" +esbuild-loader@2.13.0: + version "2.13.0" + resolved "https://registry.yarnpkg.com/esbuild-loader/-/esbuild-loader-2.13.0.tgz#f5a3602a89a3b728506ae3e1887304fffeef9270" + integrity sha512-gC9lML8RGkTSWG2pJVEOZRLMoIluq1Jd7OzzVkOZKMzbMDMWDhXEwXLs60n+aglnAYa9GVrD/UXjTHkM51nBsg== + dependencies: + esbuild "^0.11.19" + joycon "^3.0.1" + json5 "^2.2.0" + loader-utils "^2.0.0" + type-fest "^1.0.1" + webpack-sources "^2.2.0" + +esbuild@^0.11.19: + version "0.11.20" + resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.11.20.tgz#7cefa1aee8b372c184e42457885f7ce5d3e62a1e" + integrity sha512-QOZrVpN/Yz74xfat0H6euSgn3RnwLevY1mJTEXneukz1ln9qB+ieaerRMzSeETpz/UJWsBMzRVR/andBht5WKw== + escalade@^3.0.2, escalade@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" @@ -11823,6 +11840,11 @@ joi@^17.3.0, joi@^17.4.0: "@sideway/formula" "^3.0.0" "@sideway/pinpoint" "^2.0.0" +joycon@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/joycon/-/joycon-3.0.1.tgz#9074c9b08ccf37a6726ff74a18485f85efcaddaf" + integrity sha512-SJcJNBg32dGgxhPtM0wQqxqV0ax9k/9TaUskGDSJkSFSQOEWWvQ3zzWdGQRIUry2j1zA5+ReH13t0Mf3StuVZA== + jpeg-js@^0.3.4: version "0.3.6" resolved "https://registry.yarnpkg.com/jpeg-js/-/jpeg-js-0.3.6.tgz#c40382aac9506e7d1f2d856eb02f6c7b2a98b37c" @@ -11981,10 +12003,10 @@ json5@^1.0.1: dependencies: minimist "^1.2.0" -json5@^2.1.2: - version "2.1.3" - resolved "https://registry.yarnpkg.com/json5/-/json5-2.1.3.tgz#c9b0f7fa9233bfe5807fe66fcf3a5617ed597d43" - integrity sha512-KXPvOm8K9IJKFM0bmdn8QXh7udDh1g/giieX0NLCaMnb4hEiVFqnop2ImTXCc5e0/oHz3LTqmHGtExn5hfMkOA== +json5@^2.1.2, json5@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.0.tgz#2dfefe720c6ba525d9ebd909950f0515316c89a3" + integrity sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA== dependencies: minimist "^1.2.5" @@ -19210,6 +19232,11 @@ type-fest@^0.8.0, type-fest@^0.8.1: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.0.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.1.1.tgz#210251e7f57357a1457269e6b34837fed067ac2c" + integrity sha512-RPDKc5KrIyKTP7Fk75LruUagqG6b+OTgXlCR2Z0aQDJFeIvL4/mhahSEtHmmVzXu4gmA0srkF/8FCH3WOWxTWA== + type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -19996,7 +20023,7 @@ webpack-sources@^1.1.0, webpack-sources@^1.4.3: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^2.1.1: +webpack-sources@^2.1.1, webpack-sources@^2.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-2.2.0.tgz#058926f39e3d443193b6c31547229806ffd02bac" integrity sha512-bQsA24JLwcnWGArOKUxYKhX3Mz/nK1Xf6hxullKERyktjNMC4x8koOeaDNTA2fEJ09BdWLbM/iTW0ithREUP0w==