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): add ThemedImage component #3730

Merged
merged 6 commits into from
Nov 13, 2020

Conversation

Simek
Copy link
Contributor

@Simek Simek commented Nov 12, 2020

Motivation

For some websites it is important to be able to switch the image source based on the current theme. This PR introduces the simple theme helper component - ThemedImage - which allow users to specify the lightSrc and darkSrc props and switches the image content dynamically on theme change.

The docs has also been updated within this PR.

Have you read the Contributing Guidelines on pull requests?

Yes

Test Plan

Changes has been tested locally using Docusuaurus V2 website.

Preview

themedimage2

Related PRs

None.

@facebook-github-bot facebook-github-bot added the CLA Signed Signed Facebook CLA label Nov 12, 2020
@netlify
Copy link

netlify bot commented Nov 12, 2020

Deploy preview for docusaurus-2 ready!

Built with commit 80309ca

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

}
}

export default ThemedImage;
Copy link
Collaborator

Choose a reason for hiding this comment

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

This component will only render one image (the light one) on the other on server-rendered markup.

See useTheme hook:

const getInitialTheme = () => {
  if (!ExecutionEnvironment.canUseDOM) {
    return themes.light; // SSR: we don't care
  }
  return coerceToTheme(document.documentElement.getAttribute('data-theme'));
};

On hydration, the light image will be replaced with the dark one, producing an unwanted flash.

The only viable solution to fix the problem is to always render the 2 images, and display one or the other thanks to CSS (which is loaded before the user actually see anything, so there's no flash)

img.themed-image{
  display: none;
}

html[data-theme='light'] img.themed-image.themed-image--light {
  display: block;
}
html[data-theme='dark'] img.themed-image.themed-image--dark {
  display: block;
}

Note: it's worth considering that light/dark is common nowadays but the CSS spec says this might be expanded to more values in the future. Having an API surface that is more extensible could be more future-proof (ie user can declare its own theme name, we should rather have sources={dark: darkSrc,light:lightSrc,sepia: sepiaSrc})

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.

I think it would produce an hydration flash.

Can you add an example somewhere so that we can see it in action in deploy previews? (there an example md page if you want)

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

I think it would produce an hydration flash.

Can you add an example somewhere so that we can see it in action in deploy previews? (there an example md page if you want)

You can find the example in the added section on the Markdown Features page.

readonly darkSrc?: string;
} & ComponentProps<'img'>;

const ThemedImage: () => JSX.Element;
Copy link
Collaborator

Choose a reason for hiding this comment

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

shouldn't you use the props here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

?

Copy link
Collaborator

Choose a reason for hiding this comment

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

const ThemedImage: (props: Props) => JSX.Element;

Copy link
Contributor Author

Choose a reason for hiding this comment

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

This has been taken from the Tabs component types already in that file (https://github.com/facebook/docusaurus/blob/master/packages/docusaurus-theme-classic/src/types.d.ts#L395):

  const Tabs: () => JSX.Element;
  export default Tabs;

TS should be happy with that construction because this method only serves as the definition for the returned value.

@slorber
Copy link
Collaborator

slorber commented Nov 12, 2020

Ah yeah, didn't see, but unfortunately Netlify is failing so can't check the preview

12:14:04 PM: Module not found: Can't resolve '@theme/ThemedImage' in '/opt/build/repo/website/docs'
Client bundle compiled with errors therefore further build is impossible.

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

Ah yeah, didn't see, but unfortunately Netlify is failing so can't check the preview

12:14:04 PM: Module not found: Can't resolve '@theme/ThemedImage' in '/opt/build/repo/website/docs'
Client bundle compiled with errors therefore further build is impossible.

@slorber I saw the log, working on this right now. But it is annoying that during the development the app runs correctly and do not fail or warn user in any way, but on build is failing due to some ambiguous errors.

Is there any way to improve this processes and make the results of both modes (dev & build/serve) consistent?

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

Currently CI fail is related to the issue with V1 build (probably caused by mentioned above PR):

12:48:41 PM: Docusaurus v1 running as a Netlify deploy preview
12:48:41 PM: Netlify deploy previews will only deploy a subset of available versions: 1.14.6 - 1.9.x
12:48:42 PM: $ BASE_URL=/v1/ yarn build:v1
12:48:42 PM: $ yarn workspace docusaurus-1-website build
12:48:43 PM: $ docusaurus-build
12:48:44 PM: internal/modules/cjs/loader.js:583
12:48:44 PM:     throw err;
12:48:44 PM:     ^
12:48:44 PM: Error: Cannot find module '@babel/helper-skip-transparent-expression-wrappers'
12:48:44 PM:     at Function.Module._resolveFilename (internal/modules/cjs/loader.js:581:15)
12:48:44 PM:     at Function.Module._load (internal/modules/cjs/loader.js:507:25)
12:48:44 PM:     at Module.require (internal/modules/cjs/loader.js:637:17)
12:48:44 PM:     at require (internal/modules/cjs/helpers.js:22:18)
12:48:44 PM:     at Object.<anonymous> (/opt/build/repo/node_modules/@babel/preset-env/node_modules/@babel/plugin-transform-spread/lib/index.js:10:48)
12:48:44 PM:     at Module._compile (internal/modules/cjs/loader.js:689:30)
12:48:44 PM:     at Module._compile (/opt/build/repo/node_modules/pirates/lib/index.js:99:24)
12:48:44 PM:     at Module._extensions..js (internal/modules/cjs/loader.js:700:10)
12:48:44 PM:     at Object.newLoader [as .js] (/opt/build/repo/node_modules/pirates/lib/index.js:104:7)
12:48:44 PM:     at Module.load (internal/modules/cjs/loader.js:599:32)
12:48:44 PM: error Command failed with exit code 1.
12:48:44 PM: info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.
12:48:44 PM: error Command failed.
12:48:44 PM: Exit code: 1

const {isDarkTheme} = useThemeContext();
const {src, darkSrc, lightSrc, alt = '', ...propsRest} = props;

if (darkSrc && isDarkTheme) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Let's not duplicate essentially the same code, let's use a ternary expression to determine the desired image src?

And why is there a prop just src? In this case need use native <img> element, not ThemedImage.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Let's not duplicate essentially the same code, let's use a ternary expression to determine the desired image src?

I choose the standard conditional structure for the redability.

And why is there a prop just src? In this case need use native <img> element, not ThemedImage.

Because of TS types inheritance - & ComponentProps<'img'>; - and props passing. It also serves as fallback, if user provide src instead of lightSrc to the themed image.

@lex111
Copy link
Contributor

lex111 commented Nov 12, 2020

Currently CI fail is related to the issue with V1 build (probably caused by mentioned above PR):

Most likely the reason for this is Netlify cache, because the tests in the PR with the update deps were successful.

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

Currently CI fail is related to the issue with V1 build (probably caused by mentioned above PR):

Most likely the reason for this is Netlify cache, because the tests in the PR with the update deps were successful.

Hmm, interesting. Can you re-run the pipeline than?

@lex111
Copy link
Contributor

lex111 commented Nov 12, 2020

Maybe @slorber can do it, but I don't have access for that :(

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

@lex111 I have pushed the ternary refactor commit but the CI still fails on the same V1 error. Are you sure it is only the Netlify cache issue?

@slorber
Copy link
Collaborator

slorber commented Nov 12, 2020

just relaunched it

image

readonly darkSrc?: string;
} & ComponentProps<'img'>;

const ThemedImage: () => JSX.Element;
Copy link
Collaborator

Choose a reason for hiding this comment

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

despite other examples not declaring components props properly, I think we should do this (cf TabItem, Layout).

We'll make sure all comps have props: Props over time

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'm not against this change, but this sound like a feature request or a bug fix request, so I think you should handle this for all theme components in the separate PR.


<ThemedImage
alt="Docusaurus themed image"
lightSrc={useBaseUrl('img/docusaurus_keytar.svg')}
Copy link
Contributor

Choose a reason for hiding this comment

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

Why don't we just pass the path as regular string? Accordingly, inside the ThemedImage component, apply useBaseUrl to all src paths? Then users would not import useBaseUrl in their MDX doc.

Copy link
Collaborator

Choose a reason for hiding this comment

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

@lex111 totally agree with that. BTW I imagined we could even have a Docusaurus image component just for this usecase

Copy link
Contributor Author

@Simek Simek Nov 12, 2020

Choose a reason for hiding this comment

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

I'm not sure if processing the base URL inside the component is a good idea. It can blur the functionality and cause a confusion (why img need useBaseURL when ThemeImage do not need it)? The goal was to create simple helper not the robust, alternative image component.

And again, I have just looked at the way img tag is used on the Docusuaurus V2 website - https://github.com/facebook/docusaurus/blob/master/website/src/pages/index.js#L77.

Copy link
Collaborator

Choose a reason for hiding this comment

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

My long-term goal is to simply deprecate useBaseUrl(). But this can probably be done later, so if we don't apply it in the component today, it's fine

@lex111
Copy link
Contributor

lex111 commented Nov 12, 2020

@Simek sorry, CI failed again due to the Bootstrap theme. @slorber I think we need to remove it from the CI process, because in fact it is an unmaintained theme, which creates a lot of difficulties.

@slorber
Copy link
Collaborator

slorber commented Nov 12, 2020

agree, you can remove the bootstrap theme from the netlify scripts and the netlify HTML page. We'll add it back when it's more production ready

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

@slorber @lex111 It looks like useThemeContext stub is working but the CI fails back at the V1 build issue.

@Simek
Copy link
Contributor Author

Simek commented Nov 12, 2020

It looks like the local rebase and force push of PR branch fixed the V1 CI issue.

@slorber
Copy link
Collaborator

slorber commented Nov 12, 2020

@Simek I added an example so that you understand why your js-based solution will not work in all cases:

<ThemedImage
  alt="Docusaurus themed image"
  lightSrc={
    'https://images.pexels.com/photos/4734723/pexels-photo-4734723.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=200'
  }
  darkSrc={
    'https://images.pexels.com/photos/5828718/pexels-photo-5828718.jpeg?auto=compress&cs=tinysrgb&dpr=2&w=200'
  }
/>

Try refreshing this page, with dark mode, and caching enabled: https://deploy-preview-3730--docusaurus-2.netlify.app/classic/docs/markdown-features/

Here, the images added are cached aggressively. The image being served by the server will appear on the user screen before React has time to hydrate.
As the SSR theme is hardcoded, the user will always see the light image first. If he has the black theme, after hydration only, the light image will be switched to the dark image, which produces a flickering experience.

You didn't see this issue because:

  • your images are not cached, and appear after React hydration
  • your images are not at the top, and the scroll to anchor link is slightly delayed

I think the server should always render both images, but only display one thanks to CSS.
Once React has hydrated (useDocusaurusContext().isClient=true), we can only add a single image in the DOM to avoid useless resource consumption.

@Simek
Copy link
Contributor Author

Simek commented Nov 13, 2020

@slorber PR has been updated, mostly according to the review suggestions. Feel free to improve or change the docs section (I'm way better at improving the docs rather than creating the initial version).

Please remember to remove the SSR example after the successful review, but before the merge. 🙂

@slorber slorber added the pr: new feature This PR adds a new API or behavior. label Nov 13, 2020
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.

None yet

4 participants