Skip to content

Remove SVGs from JS bundle, #897

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

Merged
merged 7 commits into from
Jul 27, 2021
Merged

Remove SVGs from JS bundle, #897

merged 7 commits into from
Jul 27, 2021

Conversation

tomalec
Copy link
Contributor

@tomalec tomalec commented Jul 21, 2021

Changes proposed in this Pull Request:

  • Remove SVGs from JS bundle, Use them as <img>s.
  • Add alt texts.
  • Remove unnecessary width, height 100% styles.

Fixes #498

Screenshots:

See below

Detailed test instructions:

  1. Before pulling this branch
  2. Open the following cases in separate tabs to have something to compare trunk and fix/498-svg-out
  3. Check it looks as before (in various window sizes)
  4. Check the alt text is fine.

Cases:

  1. Marketing > GLA (Dashboard) > "Edit" Free Listings > step 2
    /wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fdashboard&subpath=%2Ffree-listings%2Fedit&pageStep=2

    Hero image

    Google Shopping search results example

  2. Marketing > GLA (Dashboard) > "Add paid campaign" > complete setup
    /wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fdashboard&guide=campaign-creation-success
    Successful guide

    Drawing of a person who successfuly launched a campaign

  3. Disconnect Google Account using Connection test page
    https://gla1.test/wp-admin/admin.php?page=connection-test-admin-page

  4. Marketing > GLA
    /wp-admin/admin.php?page=wc-admin&subpath=%2Freconnect-accounts&path=%2Fgoogle%2Fsettings
    Re-connect Google

    Google Logo

  5. Disconnect all accounts at /wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fsettings

  6. Marketing > GLA
    /wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fstart
    Screenshot from 2021-07-21 18-13-02

    Google Shopping search results example
    Drawing of jigsaw puzzles connecting together
    Drawing of a person looking at their mobile
    Drawing of a bar and line charts heading up

  7. Complete the setup
    /wp-admin/admin.php?page=wc-admin&path=%2Fgoogle%2Fproduct-feed&guide=submission-success
    Complete setup guide

    WooCommerce Logo
    Google Logo

Changelog entry

Tweak - Removed SVGs from JS bundle

Additional notes:

  1. I'm not sure if exposing /js/src/... as the assets directory is the safest and fits WordPress conventions. At least it does not require moving files around and lets us keep images close to components that use them.
  2. I think that the ideal and the cheapest for an end-user solution would be to inline SVGs into HTML. However, AFAIK we do not use any SSR, and React component instances are 100% JS (as opposed to Custom Elements, which could be served as pure HTML for the first-page load).
  3. I made up alt text by myself, so I'd appreciate an approval for the copy (in quote blocks below respective screenshots), @j111q?
  4. According to bundlewatch our index.js bundle got down by ~52% (719,79KB -> 347.81KB)

Use them as `<img>`s.
Add alt texts.
Remove unnecessary width, height 100% styles.

Fixes #498
@tomalec tomalec requested a review from a team July 21, 2021 17:07
@tomalec tomalec self-assigned this Jul 21, 2021
Copy link
Member

@eason9487 eason9487 left a comment

Choose a reason for hiding this comment

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

🎉 for the bundle size! 👍


💅
Looks like we don't need this SVG mock anymore:

'\\.svg$': '<rootDir>/tests/mocks/assets/svgrMock.js',

(Just found that the current test did not cover the importing of SVG files, but there is no need to make up then.)


🚧
The archived zip file would not include js/src/ directory.

  1. I'm not sure if exposing /js/src/... as the assets directory is the safest and fits WordPress conventions. At least it does not require moving files around and lets us keep images close to components that use them.

Probably we have to move all SVGs. If keep SVGs at their current positions, it would need to list all SVGs' paths in composer config.

We could consider a solution of centralizing the assets files by below steps:

  1. Create a assets directory at project root
  2. Add "!/assets" to the exclude list for including the assets directory into the archived zip file
    "archive": {
    "exclude": [
    "!/js/build",
    "!/vendor/*",
    "!/languages"
    ]
    },
  3. Replace the assetsURL value with $this->get_plugin_url( 'assets/' )
    'glaData',
    [
    'assetsURL' => plugin_dir_url( $this->get_main_file() ),
  4. Move SVGs to assets directory and then they could be accessed via /wp-content/plugins/google-listings-and-ads/assets/[filename].svg path
  5. Run npm run archive to see if they're included into zip file as well

@eason9487
Copy link
Member

  1. Create a assets directory at project root

Just noticed that the path /wp-content/plugins/google-listings-and-ads/assets/google-logo.svg will be blocked by some ad-block browser extensions. 🤦‍♂️ And images wouldn't be blocked.

@@ -23,7 +32,14 @@ export const APPEARANCE = {

const appearanceDict = {
[ APPEARANCE.GOOGLE ]: {
Logo: GoogleLogo,
Logo: (
Copy link
Member

Choose a reason for hiding this comment

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

💅
How about rename to logo since it's changed to an instantiated component?

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 made me think, that serving an instance is not the best solution as if we are going to create two instances of <AccountCard appearance="google">, the Logo instance would not be duplicated and appear only in one. Do I get JSX right? I just got used to using HTMLTemplateElements and instantiating them, and JSX is not a <template> ;)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

hm... 😕 it seems to eventually stamp two separate instances of <img>.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Addressed in 1ead489

Copy link
Member

Choose a reason for hiding this comment

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

Sorry, I used an imprecise term. It should be called react element if follow the definitions of React official blog. And the instance should be referred to the component instance in runtime and it keeps references to relevant DOM.

@tomalec
Copy link
Contributor Author

tomalec commented Jul 22, 2021

🚧
The archived zip file would not include js/src/ directory.

Thank you for catching this! 🙏

I'd like to try to keep the files where they are, as it reduced the need for manual syncing assets used by components => reduced the costs and risk of maintaining abandoned files.

I decided to try this path.

If keep SVGs at their current positions, it would need to list all SVGs' paths in composer config.

by adding "!/js/src/**/*.svg", to composer.json.

WDYT about it, does it pass ad blockers you checked?

@tomalec
Copy link
Contributor Author

tomalec commented Jul 22, 2021

I think it's ready for another review.

@tomalec tomalec requested a review from eason9487 July 22, 2021 13:06
@eason9487
Copy link
Member

eason9487 commented Jul 23, 2021

I'd like to try to keep the files where they are, as it reduced the need for manual syncing assets used by components => reduced the costs and risk of maintaining abandoned files.

Abandoned files maintainability

By declaring SVGs in constants or another dedicated file, and export them directly, the maintenance cost should be very small.

// constants.js
const { assetsURL } = glaData;

export const svgGoogleLogo = assetsURL + 'gogole-logo.svg';
export const svgHero = assetsURL + 'google-free-listing.svg';

// Consumer side

import { svgGoogleLogo } from '.~/constants';

When checking whether a file is still in use, we could:

  1. Global search the constant \bsvgGoogleLogo\b if editor supports regexp search
  2. Comment out export to see if a warning shows up from the webpack building
    image

Code maintainability

Regardless of whether the variables are declared in the components that use them, or in the constants.js, only the filenames are required, or at most additional subpath in the assets directory.

Compare with the current method, it can eliminate the need for comments scattered in several places and the need to write the file paths under js/src individually as below.

/**
 * Full URL to the Google G logo image.
 * Preferably we would inline it into HTML to reduce the bundle size.
 *
 * Unfortunately, React does not support `import.meta`, so we need to hardcode the module path.
 */
const googleLogoURL =
	glaData.assetsURL + 'js/src/components/account-card/gogole-g-logo.svg';

In terms of overall maintainability, such as adding new images (not only svg, maybe png or jpg, etc), moving files, and those additional comments, I think it's worth it to do a one-time move. WDYT?

@eason9487
Copy link
Member

💅
Thanks for adding this c101d69. I have one more small nitpick that the tests/mocks/assets/svgrMock.js file could be removed as well.

@tomalec
Copy link
Contributor Author

tomalec commented Jul 27, 2021

Abandoned files maintainability

[…]
When checking whether a file is still in use, we could:

Sorry, I didn't state that clearly, but my goal was to remove the need of "checking whether a file is still in use" totally. Consider the scenario:

It happens that <AccountCard> is no longer needed. As a developer, all I need to do is:

  1. Remove the js/src/components/account-card/ folder

(our linters will make sure nothing is importing anything from there)

As opposed to the solution you propose where

  1. Remove the js/src/components/account-card/ folder.
  2. Check for any images imported by any file in js/src/components/account-card/
    1. For every image:
      1. Global search the constant \bvariableName\b if editor supports regexp search
      2. Comment out export to see if a warning shows up from the webpack building
      3. Remove the export from '.~/constants' if there is nobody else using them

So to me "By declaring SVGs in constants or another dedicated file, and export them directly, the maintenance cost" when removing the component would increase from O(1) to O(n^2)

Code maintainability

Regardless of whether the variables are declared in the components that use them, or in the constants.js, only the filenames are required, or at most additional subpath in the assets directory.

Sure, only filenames are required. But in my opinion, additional folder structure gives valuable information. By putting everything to a single flat bag of /images we would reduce discoverability and identification of images. Just like we don't put all *.js files in a single /js folder, we shouldn't put all *.(svg|png) files in a single folder. Especially, that none of those images is shared as itself. Only the components are shared.

Compare with the current method, it can eliminate the need for comments scattered in several places and the need to write the file paths under js/src individually as below.

I agree. Moreover, this comment might have some sense for a single-use, but repeating it everywhere, and complaining that React does not follow ECMA standards, and produces JS instead of HTML, does not bring any value. I'll remove them.

tomalec added 2 commits July 27, 2021 11:41
as repeatedly complaining that React does not follow ECMA standards, and produces JS instead of HTML, does not bring much value.

To partially address #897 (comment)
@tomalec
Copy link
Contributor Author

tomalec commented Jul 27, 2021

Another thing to note in favor of ~/js/src/components/**/*.(svg|png) and against /images/*.(svg|png) is that:

  • it keeps files closer to where they are used,
  • makes component more atomic, independent, and self-sufficient

Copy link
Member

@eason9487 eason9487 left a comment

Choose a reason for hiding this comment

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

I have got @tomalec's perspective from #897 (comment). Considering that the current implementation has already addressed the issue #498, I think there's no blocker for now.

@tomalec tomalec merged commit 55ba670 into trunk Jul 27, 2021
@tomalec tomalec deleted the fix/498-svg-out branch July 27, 2021 13:58
@eason9487
Copy link
Member

@tomalec

🚧

Sorry, I just noticed that the google-listings-and-ads.zip file should be about the same size as the original, but the reduced file size is almost the same as the index.js. After running npm run archive on my end, the archive file doesn't include the src/js directory. I suspected that the rule of archive.exclude doesn't support ** to find files recursively.

"!/js/src/**/*.svg",

After deeper investigation about composer archive, here are my findings:

I haven't look into it but seems the file-loader might be another solution if want to keep images stay with needed components.

@tomalec
Copy link
Contributor Author

tomalec commented Jul 28, 2021

@eason9487 I'm sorry, I missed it again :(
Thank you for finding that and an awesome investigation.

@tomalec
Copy link
Contributor Author

tomalec commented Jul 28, 2021

To add to your investigation composer archive does not allow to set $strictWildcardSlash = false, to allow to mimic recursive wildcard https://github.com/symfony/Finder/blob/5.3/Glob.php#L79.

Thanks for finding file-loader -> asset-modules.
Usually, I'm against over-webpackign everything. Especially, using more webpack to solve problems created by using webpack. (If we would write build-less, or at least webpack-less, and use vanilla JS ES Modules, we could use import.meta to get the current path).

But, given where we are, this seems to be a nice solution for our problem:

  • lets us keep source files close to the code,
  • could preserve or change the path of distributed assets
  • could generate a content hash, which would allow merchants to enable strict caching rules.
    That hurts my old-school web-dev sentiment of a browser running same files I use during development. And could make production debugging a tad complicated.
    We can emit paths using the original path and filename, like: /images/{path}/{contenthash}.{filename}.{ext}. That way the files would be still matchable by a human, and the merchant would gain caching possibilities.

The only doubt I have is that we are still on Webpack 4, so we are forced to use something that we now are deprecated and has a cleaner alternative in the future. That kind of introduces additional technical debt.

tomalec added a commit that referenced this pull request Jul 28, 2021
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Don't bundle SVG Images in JS build
2 participants