Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(v2): Allow configuring babel via babel.config.js #2903

Merged
merged 4 commits into from
Jun 12, 2020
Merged

feat(v2): Allow configuring babel via babel.config.js #2903

merged 4 commits into from
Jun 12, 2020

Conversation

SamChou19815
Copy link
Contributor

@SamChou19815 SamChou19815 commented Jun 7, 2020

Motivation

Close #2772. Allow user to configure babel using babel.config.js. For backward compatibility, lack of this file will automatically fallback to default config.

Using advice from @slorber, it is implemented in a way without exposing isServer internals to the end user.

Have you read the Contributing Guidelines on pull requests?

Yes

Test Plan

No custom babel config

The current repo doesn't use custom babel config, and the preview site can still build.

With custom babel config

  1. Remove website/babel.config.js and yarn start, website can still load.
  2. Add the following code with flow specific syntax to the top of website/src/pages/index.js
// @flow

type FlowTypeExample = {| optional: ?string |};

const a: FlowTypeExample = {};
const b: FlowTypeExample = {optional: ''};
  1. yarn start, and observe that the site failed to load with syntax errors.
  2. yarn workspace docusaurus-2-website add @babel/preset-flow
  3. Change website/babel.config.js to:
modole.exports = {
  presets: [
    require.resolve('@babel/preset-flow'),
    require.resolve('@docusaurus/core/lib/babel/preset')
  ]
}
  1. yarn start, and observe that the site can correctly load.

Related PRs

(If this PR adds or changes functionality, please take some time to update the docs at https://github.com/facebook/docusaurus, and link to your PR here.)

@facebook-github-bot facebook-github-bot added the CLA Signed Signed Facebook CLA label Jun 7, 2020
@docusaurus-bot
Copy link
Contributor

docusaurus-bot commented Jun 7, 2020

Deploy preview for docusaurus-2 ready!

Built with commit aba33b4

https://deploy-preview-2903--docusaurus-2.netlify.app

@SamChou19815 SamChou19815 marked this pull request as ready for review June 7, 2020 21:06
@SamChou19815 SamChou19815 requested a review from yangshun as a code owner June 7, 2020 21:06
@yangshun yangshun requested a review from slorber June 8, 2020 01:47
@yangshun
Copy link
Contributor

yangshun commented Jun 8, 2020

Thanks Sam, will leave to @slorber to review!

presets: [
'@babel/preset-flow',
// The following lines are necessary,
// unless you want to configure Babel from scratch.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it possible to somehow add custom Babel plugins without adding these lines? I feel this is a little verbose.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There is a package babel-merge that seems to do the job. However, it is not maintained by babel so I'm not sure whether we should use it, instead of relying on babel's preset merging mechanism that is guaranteed to work.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on usages/issues that seems pretty reliable, but I think we should build our own preset so that we don't need this, as discussed here: #2903 (comment)

@slorber
Copy link
Collaborator

slorber commented Jun 9, 2020

Hi @SamChou19815

TLRD: I think we should build our own babel preset

Your current solution works but I'm not totally fan of the API.

Unfortunately, we cannot exactly follow the approach by next.js, since docusaurus's existing babel config is not static: it's behavior is determined by isServer. Therefore, there is no way that we can let user specify a .babelrc or even babel.config.js without breaking the babel config semantics, since the isServer logic is unknown to Babel.

Actually, I was surprised by what you said here, as I've seen many isServer or isProduction things in many fwk codebases (Next, Gatsby, Expo, RN/Metro...) codebase.

I think the approach used by Next is suitable for our usecase. It uses the "caller" concept of Babel to pass flags to the preset, so that the preset can have a dynamic configuration:

https://babeljs.io/docs/en/options#caller


Here's what I would try:

Create a Docusaurus preset

Use the caller api to know if the preset is used in server/client mode, dev/prod mode, and create the appropriate babel config.

  const isServer = api.caller((caller: any) => !!caller && caller.isServer)
  const isDev = api.caller((caller: any) => !!caller && caller.isDev)

Next does this: https://github.com/vercel/next.js/blob/31b3e46b8f08f3ae8c16d95f4379f3129324a9be/packages/next/build/babel/preset.ts#L66

Alternative, the babel loader already injects a caller.target === "node" / "web" so that you know if isServer is true in the preset
https://github.com/babel/babel-loader#customize-config-based-on-webpack-target

Use the preset in the Babel webpack loader

The loader option should tell the Docusaurus preset in which mode it is

      babelLoaderOption.caller.isServer = isServer
      babelLoaderOption.caller.isDev = development

Next does this:
https://github.com/vercel/next.js/blob/canary/packages/next/build/webpack/loaders/next-babel-loader.js#L136

Add a .babelrc with the Docusaurus preset:

{
  "presets": [
    [
      "docusaurus/babel",
      {presetOptions: 42}
    ]
  ],
  "plugins": ["my-other-babel-plugin"]
}

This is the public api that most users would expect.

Next does this:
https://github.com/vercel/next.js/blob/master/docs/advanced-features/customizing-babel-config.md


I think we could by default add a babel.config.js with this new Docusaurus preset on every new D2 project so that it's intuitive for the users they can add babel plugins here.

For retrocompatibility we could default to a config with just the preset. I've seen this applied in Metro for RN here: https://github.com/facebook/metro/blob/master/packages/metro-react-native-babel-transformer/src/index.js#L91

What do you think @SamChou19815 @yangshun @lex111 ?

presets: [
'@babel/preset-flow',
// The following lines are necessary,
// unless you want to configure Babel from scratch.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

based on usages/issues that seems pretty reliable, but I think we should build our own preset so that we don't need this, as discussed here: #2903 (comment)

@slorber
Copy link
Collaborator

slorber commented Jun 9, 2020

I've also looked at other solutions:


Gatsby

Gatsby has a dedicated lifecycle api to update the webpack/babel config directly at the plugin level. Probably more advanced than we want here.

The Gatsby setup is quite complicated, with multiple babel stages (one for example is dedicated to extract the static queries from the code).

It does not seem to use the caller api but rather build the final config (with its own babel config merge logic), and then call the different babel stages one by one, with this config.

https://github.com/gatsbyjs/gatsby/blob/master/packages/babel-preset-gatsby/src/index.js

Also, in my experience trying to use a .babelrc in it, it didn't work great ^^

gatsbyjs/gatsby#21157

Not sure we should be inspired by Gatsby for these reasons.


Expo

Expo uses the caller api so that the preset can know for which platform it builds (ios/android/web) and adapt the preset accordingly

https://github.com/expo/expo-cli/blob/master/packages/webpack-config/src/loaders/createBabelLoader.ts#L204

https://github.com/expo/expo/blob/master/packages/babel-preset-expo/index.js#L8

@SamChou19815
Copy link
Contributor Author

@slorber updated my implementation!

@SamChou19815 SamChou19815 requested a review from slorber June 10, 2020 02:39
@SamChou19815 SamChou19815 changed the title feat(v2): Allow configuring babel via docusaurus.config.js feat(v2): Allow configuring babel via babel.config.js Jun 10, 2020
Copy link
Collaborator

@slorber slorber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great!

Tested locally and it worked fine.

Just remove the configureBabel

Other changes are more subjective, as the feature is working correctly

Do you think it could be useful to publish the preset as a separate package? I don't think it would be useful, but others are doing this so... don't know the reasons

packages/docusaurus/src/server/config.ts Outdated Show resolved Hide resolved
BABEL_CONFIG_FILE_NAME,
));
} catch {
customBabelConfiguration = undefined;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

works but not fan of using exception flow, I'd rather use fs.existsSync(path)

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should not read/require that file, we could just check if it exists and if it does, put configFile: true in babel so that it picks it automatically

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

babelrc: false,
configFile: false,
caller: {name: isServer ? 'server' : 'client'},
}),
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

if the config file exist, what about configFile: true (or providing its path explicitly?)

I don't like so much the idea to have a custom assign/merge here (but it probably does not matter much anyway)

```js title="babel.config.js"
module.exports = {
presets: [require.resolve('@docusaurus/core/lib/babel/preset')],
};
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

require.resolve() is for yarn2 right?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes

@SamChou19815
Copy link
Contributor Author

Do you think it could be useful to publish the preset as a separate package? I don't think it would be useful, but others are doing this so... don't know the reasons

It's definitely easier to keep it within @docusaurus/core. Next.js also doesn't publish its preset separately.

@SamChou19815 SamChou19815 requested a review from slorber June 10, 2020 23:04
Copy link
Collaborator

@slorber slorber left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All seems good, but I'd rather simplify getBabelLoader signature before merge

options: Object.assign(
export function getBabelLoader(
isServer: boolean,
babelOptions?: TransformOptions | string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's not worth to keep the babelOptions object here, as it's an internal method and it won't ever be called with an object.

I'd rather have:

export function getBabelLoader({isServer, configFile}: {isServer: boolean, configFile?: string}) {

}

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not completely internal. It has been exposed here: https://v2.docusaurus.io/docs/lifecycle-apis#configurewebpackconfig-isserver-utils

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah :) that's fine then, I guess we need to be retrocompatible

options: Object.assign(
export function getBabelLoader(
isServer: boolean,
babelOptions?: TransformOptions | string,
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah :) that's fine then, I guess we need to be retrocompatible

@yangshun
Copy link
Contributor

@slorber feel free to merge it, you're also a maintainer and you're familiar with the code already :)

@yangshun yangshun merged commit 729b3ca into facebook:master Jun 12, 2020
@yangshun yangshun added the pr: new feature This PR adds a new API or behavior. label Jun 12, 2020
@SamChou19815 SamChou19815 deleted the allow-configure-babel branch June 12, 2020 04:05
@slorber
Copy link
Collaborator

slorber commented Jun 12, 2020

I'm afraid to forget squashing now ;)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
CLA Signed Signed Facebook CLA pr: new feature This PR adds a new API or behavior.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Configurable Babel Plugins
6 participants