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): docusaurus-plugin-client-redirects #2793

Merged
merged 46 commits into from
Jun 10, 2020

Conversation

slorber
Copy link
Collaborator

@slorber slorber commented May 22, 2020

First draft of plugin to handle client side redirects

I tried to make it flexible, and testable (tests will come)

It should cover basic redirections with ease, like path.html -> path
It should also cover the Watchman case: path -> path.html

Not ready to run, but you can take a look and give me some early feedback @yangshun ?

@slorber slorber requested a review from yangshun as a code owner May 22, 2020 18:16
@facebook-github-bot facebook-github-bot added the CLA Signed Signed Facebook CLA label May 22, 2020
@slorber slorber marked this pull request as draft May 22, 2020 18:17
@docusaurus-bot
Copy link
Contributor

docusaurus-bot commented May 22, 2020

Deploy preview for docusaurus-2 ready!

Built with commit 6b50763

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

Copy link
Contributor

@lex111 lex111 left a comment

Choose a reason for hiding this comment

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

I think this is an awesome feature, but for a complete understanding, I would like to see tests or/and draft doc.
Although we can make some improvements now.

@slorber
Copy link
Collaborator Author

slorber commented May 24, 2020

Thanks for the feedback @lex111

It's not ready and not tested, just wanted to let you see a preview of the API I'm crafting (actually didn't even run the code yet so it might have bugs)

Will handle your feedbacks tomorrow.

@slorber
Copy link
Collaborator Author

slorber commented May 25, 2020

Hi @lex111

I've added the tests and made the code more robust, it's almost finished. Will finish tomorrow.
It still has some bugs to fixes, and the write side involving fs is not tested yet).

When this feature is complete, using the following (main usecase)

    [
      '@docusaurus/plugin-client-redirects',
      {
        fromExtensions: ['html'],
      },
    ],

Should redirect from /path.html to /path and write a /path.html/index.html file (or maybe just /path.html ?)

But it should also support the "Watchman" case (#2697) that wants .html urls being the canonical ones:

    [
      '@docusaurus/plugin-client-redirects',
      {
        toExtensions: ['html'],
      },
    ],

Should redirect from /path to /path.html and write a /path/index.html file

For power users, it's also possible to provide a function:

    [
      '@docusaurus/plugin-client-redirects',
      {
        createRedirects: (existingPath) => {
           return ["existingPath" + "/someFancyPathSuffix"];
        },
      },
    ],

What do you think of this API?


Also, a subtility is to make sure the user does not create "bad redirects" that would override existing html files, or having duplicate redirects. For now I just filter those, but maybe there's a good way to warn about those issues?

@slorber
Copy link
Collaborator Author

slorber commented May 26, 2020

So I think it's working fine now, and has good test coverage.

I propose to dogfood on the docusaurus v2 to redirect /path.html to /path, so that we know it keeps working over time.

It's already in the PR: https://deploy-preview-2793--docusaurus-2.netlify.app/docs/next/installation.html

I'll try the plugin on the Watchman site now and see if I have any issue.

And I'll write the doc.

@slorber slorber marked this pull request as ready for review May 26, 2020 19:00
@slorber slorber requested a review from lex111 May 26, 2020 19:00
@lex111
Copy link
Contributor

lex111 commented Jun 3, 2020

I'm thinking on making the createRedirects function more consistent, what do you think of:

I don’t know what are the advantages of this approach?

By the way, after the merging of #2861, it would be nice to add redirects to v2 website. I wonder if it is possible to use regular expressions for this purpose (to cover versioned docs)?

https://v2.docusaurus.io/docs/introduction -> https://v2.docusaurus.io/docs/
https://v2.docusaurus.io/docs/2.0.0-alpha.55/introduction -> https://v2.docusaurus.io/docs/2.0.0-alpha.55/
https://v2.docusaurus.io/docs/2.0.0-alpha.54/introduction -> https://v2.docusaurus.io/docs/2.0.0-alpha.54/
https://v2.docusaurus.io/docs/2.0.0-alpha.50introduction -> https://v2.docusaurus.io/docs/2.0.0-alpha.50/
https://v2.docusaurus.io/docs/2.0.0-alpha.48/introduction -> https://v2.docusaurus.io/docs/2.0.0-alpha.48/
https://v2.docusaurus.io/docs/next/introduction -> https://v2.docusaurus.io/docs/next/

I did something similar when I needed to correctly highlight active items in navbar, so I'm curious if redirects could be added this way (using regex).

@slorber
Copy link
Collaborator Author

slorber commented Jun 3, 2020

Hi @lex111

Here are some updates:

  • a new redirects plugin option, as you requeted
  • improved documentation an example with the 4 plugin options used together
  • stricter validation: it's barely impossible for the user to use badly formatted options
  • improved error messages
  • non-blocking warnings in case some redirects get filtered for security (ie that would lead to FS files overwriting each thers)
  • did not change the createRedirects (cf comment above)

Here's the config deployed in the PR, so that we can test better on D2 website:

[
      '@docusaurus/plugin-client-redirects',
      {
        fromExtensions: ['html'],
        redirects: [
          {
            to: '/',
            from: [
              '/plugin-client-redirects-tests/toHome1',
              '/plugin-client-redirects-tests/toHome2',
            ],
          },
          {
            to: '/docs',
            from: '/plugin-client-redirects-tests/toDocs1',
          },
          {
            to: '/docs',
            from: '/plugin-client-redirects-tests/toDocs2',
          },
          {
            to: '/docs',
            from: '/plugin-client-redirects-tests/toHomeDuplicatePath',
          },
        ],
        createRedirects: function (existingPath) {
          if (existingPath === '/') {
            return [
              '/',
              '/docs',
              '/plugin-client-redirects-tests/toHome3',
              '/plugin-client-redirects-tests/toHome4',
              '/plugin-client-redirects-tests/toHomeDuplicatePath',
            ];
          }
        },
      },
    ],

The following redirects work:

Html extension redirects:

Other redirects:

(the weird url lowercasing is related to Netlify, that's annoying with caps in pathname, shouldn't happen on other hosts)

As the config creates twice the toHomeDuplicatePath, and tries to redirect from / and /docs (existing paths that should not be overwritten by a redirect file), the build outputs the following warnings:

image

Do you see other things to handle?

@slorber
Copy link
Collaborator Author

slorber commented Jun 3, 2020

About the version thing, I thought about it, and agree that's something we could support.
What about adding this in a new PR? As this one starts to get fat and we are waiting for it to fix the Watchman sites redirects.

It's already possible to add version redirects with the custom createRedirects fn in userland.
But I agree this is a pattern that we should make it simpler to configure.
I think we should take some time to design the behavior of such feature.

@lex111
Copy link
Contributor

lex111 commented Jun 3, 2020

Honestly, this plugin really came out huge and strong I thought there would be much less code in order to implement it. We actually did not pay so much attention to error handling in our existing plugins. However, I can’t say that this is a bad approach, just before that we did not attach so much importance to this.

I actually trust in a high test coverage of this plugin, since mentally this plugin is not easy to understand (I mean me). But this is really cool, although maybe we could simplify some things, for example, is it really necessary to use Yup? Although it may be necessary to go this way in our other plugins. I mean, maybe we should try to reconsider the development process of our plugins so that they are like this redirect plugin -- a lot of tests, a thorough validation of errors, a lot of high-level abstractions... Apparently I got carried away 😄

Okay, tomorrow I'll see it again in action and probably we can already merge it into master branch.

P.S. But please add redirects for introduction path. This will also be an example for release notes.

@slorber
Copy link
Collaborator Author

slorber commented Jun 4, 2020

Hi @lex111

Honestly, this plugin really came out huge and strong I thought there would be much less code in order to implement it.

That's true, initially thought it would be smaller 😅

The size comes from giving more control to the user, and giving shortcuts for common patterns, with the fromExtensions/toExtensions.

We actually did not pay so much attention to error handling in our existing plugins. However, I can’t say that this is a bad approach, just before that we did not attach so much importance to this.

for example, is it really necessary to use Yup?

Although it may be necessary to go this way in our other plugins. I mean, maybe we should try to reconsider the development process of our plugins so that they are like this redirect plugin -- a lot of tests, a thorough validation of errors, a lot of high-level abstractions...

That's true. I did more user input validation in this plugin than other plugins. Personally I'd rather validate all user inputs on all plugins, fail-fast, and give actionable error messages to the user whenever possible, as it would lead to less github issues on the long term.

If you feel it's too much, not worth it, and not the direction we want to take, I'm ok with that and could remove all these validations.

since mentally this plugin is not easy to understand (I mean me)

a lot of high-level abstractions

I agree that it's not so easy to reason about this plugin, there are more hidden subtilities than I thought. Myself I often get confused, particularly inverting the from/to of the plugin. Maybe it's not the correct terminology to use, and we could find simpler wordings (like sourcePath/targetPath or something?)

About the abstractions, I don't think there's a lot of them, the only thing I really abstracted is the "redirectCreator" concept, which is function(path): redirects, and permit to fit the user provided createRedirects function and the "shortcuts" fromExtensions / toExtensions under the same interface.

I can remove this abstraction if you feel it's too hard to reason about. That means I'll need to write less generic code here:

function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
  const redirectsCreators: RedirectsCreator[] = buildRedirectCreators(
    pluginContext.options,
  );

  const optionsRedirects = collectPluginOptionRedirects(pluginContext);

  const redirectCreatorsRedirects = flatten(
    redirectsCreators.map((redirectCreator) => {
      return createRoutesPathsRedirects(redirectCreator, pluginContext);
    }),
  );

  return [...optionsRedirects, ...redirectCreatorsRedirects];
}

That would become something like:

function doCollectRedirects(pluginContext: PluginContext): RedirectMetadata[] {
  return [
    ...collectToExtensionsRedirects(pluginContext),
    ...collectFromExtensionsRedirects(pluginContext),
    ...collectPluginOptionRedirects(pluginContext),
    ...collectCreateRedirects(pluginContext),
  ];
}

I think you are right, that may be simpler to understand, and this abstraction may not be worth it.

Do you agree I should refactor?

@slorber slorber mentioned this pull request Jun 4, 2020
pluginContext.options.createRedirects,
),
];
}
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

@lex111 after removal of createRedirects abstraction

@slorber
Copy link
Collaborator Author

slorber commented Jun 4, 2020

@lex111

I've removed the "redirectsCreator" abstraction, that simplified a bit the code.

Do you see other things to change?

For the doc home redirects, blocked by #2879
Maybe we can merge and fix this redirection later if needed?

Copy link
Contributor

@lex111 lex111 left a comment

Choose a reason for hiding this comment

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

@yangshun can you review the docs?

Copy link
Contributor

@yangshun yangshun left a comment

Choose a reason for hiding this comment

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

Thanks for this @slorber. Will leave it to you to self-merge.

website/docusaurus.config.js Outdated Show resolved Hide resolved
website/docusaurus.config.js Outdated Show resolved Hide resolved
@slorber
Copy link
Collaborator Author

slorber commented Jun 10, 2020

@lex111 FYI I added a redirect from /introduction:


const versions = require('./versions.json');

const allDocHomesPaths = [
  '/docs',
  '/docs/next',
  ...versions.slice(1).map((version) => `/docs/${version}`),
];

    [
      '@docusaurus/plugin-client-redirects',
      {
        fromExtensions: ['html'],
        createRedirects: function (path) {
          // redirect to /docs from /docs/introduction,
          // as introduction has been made the home doc
          if (allDocHomesPaths.includes(path)) {
            return [`${path}/introduction`];
          }
        },
      },
    ],

@slorber slorber merged commit 68a1bb1 into facebook:master Jun 10, 2020
@slorber
Copy link
Collaborator Author

slorber commented Jun 10, 2020

Finally merged !

@yangshun @lex111 should we make a release or we wait for the DocSearch V3 PR to be merged?

I've seen it in the milestone here: https://github.com/facebook/docusaurus/milestone/9

@lex111
Copy link
Contributor

lex111 commented Jun 10, 2020

@slorber aaaah! Why didn’t you squash all the commits into one when merging PR? I mean, this is the default option.

Currently, in master branch 45 extra commits! 😱

image

@slorber
Copy link
Collaborator Author

slorber commented Jun 10, 2020

oooh nooo :/ not used to this workflow

actually it's not the default option for me, will look to make it the default, sorry :/

@lex111
Copy link
Contributor

lex111 commented Jun 10, 2020

So what do we do to fix this?

@slorber
Copy link
Collaborator Author

slorber commented Jun 10, 2020

@lex111 discussed with @yangshun and @JoelMarcey , it's not a big deal for this time and they enabled the option to only allow squash merge for the future

they prefere to not force push to master (there's a security)

sorry about that :/

@slorber
Copy link
Collaborator Author

slorber commented Jun 10, 2020

If needed, I have a clean branch that could be force-pushed to master here: https://github.com/slorber/docusaurus/commits/master-backup

@JoelMarcey
Copy link
Contributor

I don't believe this causes much of a technical issue, requiring us to revert or force push. It might just make the commit log / history / etc a bit less clean.

@lex111 Are you seeing issues either in this repo or your fork because of the merge commit (instead of the squash)?

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.

6 participants