Miłosz Orzeł

.net, js, html, arduino, java... no rants or clickbaits.

Notes From Migrating React App From CRA to Vite

INTRO

I've recently made the decision to migrate my vim.morzel.net pet-project from Create React App to Vite. To be honest, I was quite content with how CRA (with a bit of React App Rewired) functioned, and updating might not have been necessary. However, I wanted to use this migration as practice before possibly employing Vite on something more serious (where the speed and active development of Vite might prove a blessing).

Here's a summary of the front-end part of vim.morzel.net as reported by cloc on src directory:

-------------------------------------------------------------------------------
Language                     files          blank        comment           code
-------------------------------------------------------------------------------
TypeScript                      94            535             88           3382
CSS                             32            268             10           1613
JavaScript                       3             71             26            364
Markdown                         1             28              0             58
JSON                             1              0              0             15
SVG                              1              0              0              1
-------------------------------------------------------------------------------
SUM:                           133            902            124           5433
-------------------------------------------------------------------------------

The project is done mostly with TypeScript and CSS (modules). JS lines come primarily from glue code for WebAssembly module for drills (which is done in Rust in separate repository).

Most notable dependencies are Redux (with Toolkit), React Syntax Highlighter, Fontsource and React Testing Library. Full list with links is at the bottom of this page.

As you can see, the project is small, but nonetheless useful as a testing ground for migration to Vite.

Application is deployed on DigitalOcean droplet with Ubuntu 20.04, NGINX 1.18.0, Node.js 17.3.0 and PM2 5.10.

In this post I'll note my approach for the migration (which worked quite well) and the issues I ran into. Hopefully you will find some useful information here in case you plan to switch to Vite too.

 

PREPARING FOR MIGRATION

Switching dev/build tooling sounded like a good occasion to upgrade the React version too (from 16.9.0 to 18.2.0). This went surprisingly smoothly, I just needed to update to new application root (details here).
Once on latest React, I went for newest Jest and React Testing Library without any blockers (wasn't sure yet about trying Vitest).

 

A CLEAN SLATE

Initially the plan was to replace CRA features with Vite as described in this article or this one, but then I thought that I'm too much of a noob in Vite and I would rather start with a nice and clean Vite project and move the screens to that fresh setup. So I've created a branch for the Vite version and put there only the results of running Vite scaffolding (v4.3.9) for React with TypeScript and SWC. Then I checked out the CRA version (master branch) to another folder so I could easily move files around and have the two applications running side by side for comparison. Putting content from old version to the new was quite easy: basically copy & paste of folders with features, utils and overall app setup (like Redux store). Then copy some style and config files, favicon etc. A bit more attention was needed for putting chunks of code in index.html, src/main.tsx and src/App.tsx. I of course had to install a couple of dependencies that the app needed but were not part of Vite scaffolding...

 

SOME HURDLES

There were a couple of things that required adjustments, here they are in random order - I might have forgot some, sorry :)

Port configuration

By default Vite runs dev server on port 5173, CRA does it on 3000. Since I had a couple of bookmarks with :3000 I wanted to keep it. It's easy to change: just add this to your vite.config.ts:

server: {
  port: 3000,
  open: true
}

The open flag will automatically launch browser when server starts (just like CRA does by default).

You can also add a config for running production build locally with npm run preview (no need for installing serve like in CRA):

preview: {
  port: 3000,
  open: true
}

Accessing config from .env files

The CRA version used .env files (like .env.development or .env.production) with the help of env-cmd package. Chosen values (prefixed with REACT_APP_) were automatically included in UI and accessed like this: process.env.REACT_APP_SOMETHING in code. Fortunately Vite has built-in support for .env files (thanks to dotenv), but exposed values should be prefixed with VITE_. Access to values is slightly different, there is no process.env but you should use import.meta.env (for example: import.meta.env.VITE_SOMETHING)

DEV or PROD?

In CRA one could check process.env.NODE_ENV value to see if application was build in development or production mode. In Vite this should be changed to check of import.meta.env.DEV or import.meta.env.PROD boolean properties.

No need for %PUBLIC_URL%

While copying some things into new index.html file form CRA version I forgot to remove %PUBLIC_URL% placeholders, these should not be present in Vite version.

Injecting values into views

In CRA I've used preval.macro to inject build timestamp into a diagnostic feature. Maybe it was possible to make it work in Vite too but I went with source transform in vite.config.ts instead:

plugins: [
  react(),
  {
    name: 'build-timestamp-placeholder',
    transform(src, id) {
      if (id.endsWith('Footer.tsx') && src.includes('BUILD-TIMESTAMP-PLACEHOLDER')) {
        const date = new Date();
        return src.replace('BUILD-TIMESTAMP-PLACEHOLDER', date.toISOString() + date.getTimezoneOffset());
      }
    }
  }
]

Update (2024-01-28): You can also define global constant replacement to have a built timestamp. This Stack Overflow answer describes it nicely, it looks like a better solution. I'm leaving the part above because it won't hurt to know how to write a source transform :)

Update (2024-05-02): There's one more thing you might want to tweak in the config: generated CSS module class names.

Missing robots

When I run Lighthouse on Vite version, it told me that the app was missing robots.txt file (bad from search engines perspective). Indeed, the file was missing, so I copied it to the public folder from CRA version and problem went away.

Missing caching on static files

In CRA version, the static files of built application (*.js, *.css, *.wasm, *.woff2...) were kept in folder named static. In Vite this went to a folder named assets and made Lighthouse rightfully angry about missed opportunity for caching static resources. Since I run my app on NGINX, I had to update a rule that sets caching headers on files from folder named static to the assets folder.

This is how traffic looked like before enabling caching on static files:

Trafic without caching... Click to enlarge...

and this is with caching:

Trafic with caching... Click to enlarge...

Build output directory

When you run a build on CRA the results go into a directory named build, in Vite it goes to dist. I've had a few scripts that assumed build so these had to switch to dist.

Precise file extensions

Vite doesn't like it when a file has *.js or *.ts extension but contains JSX. It turned out that I had one rogue file like it, renamed, fixed.

Tweaking lint rules

The default ESLint rules in Vite were a bit too harsh for my toy project, so I got to add a few exceptions into .eslintrc.cjs config (like '@typescript-eslint/no-non-null-assertion': 'off')

Switching to Vitest

I've heard some good things about Vitest and since full integration of Jest into Vite is currently a bit complicated I decided to give Vitest a try.

This is the test config I have in vite.config.ts:

test: {
  globals: true,
  environment: 'jsdom',
  setupFiles: './src/test/setup.ts',
  // you might want to disable it, if you don't have tests that rely on CSS
  // since parsing CSS is slow
  css: true
},

and this is the entire content of import src/test/setup.ts;

import '@testing-library/jest-dom';

Aside of config, I had to change usage of jest.fn() to vi.fn() to make the test that use function mocks work.

Oh, one more thing: in CRA/Jest test run complained about one of the files generated for WebAssembly module (maybe it could've been fixed with transformIgnorePatterns in Jest config) but in Vite/Vitest I don't see this problem anymore. 

 

VITE ON MASTER

Once I was happy with how the app was working on a branch created for migration it was time to put Vite on the main (master) branch. This was very easy with the use of merge --strategy=ours (details) because I refrained from making any changes on master while working on the migration. After the merge, I have a clean Vite setup while Git history of files that existed before migration is preserved. Nice.


RESULTS

On localhost

Here's some comparison between the CRA and Vite versions while working on localhost (both after migration to React 18.2.0):

  • Running production build (average of 3 runs, duration as reported by real line in Linux time command output): CRA: 9s, Vite: 4.5s.
  • Starting production build locally with serve -s build in CRA and npm run preview in Vite (average of 3 runs, with time counted from running the command to a functional app appearing in new Chrome tab): CRA 3s, Vite: 0.8s
  • Observing change in a screen while running in DEV mode (hot module reload): CRA: 0.5s, Vite: practically instant :)
  • Running tests: well, frankly speaking I have (currently, yeah) very little tests on that hobby project so I can't offer very meaningful numbers, I can only tell that Vite/Vitest looks about 2.5x faster than CRA/Jest.

To sum up: Vite is noticeably faster but that doesn't mean that CRA version is slow (the difference will start to be significant on a larger application).

On server

What about the deployed application? Unfortunately I don't have data from CRA before updating React so here are the Lighthouse (navigation/desktop with forced clear storage) scores from CRA on React 16.9.0 (left) and Vite on 18.0.2 (right):

Lighthouse scores for CRA vs Vite (navigation, desktop, with forced cache clear)... Click to enlarge...


These are the results for Vite on mobile (sorry, no data for CRA version):

Lighthouse scores for Vite (navigation, mobile, with forced cache clear)... Click to enlarge...


This is the result for mobile but without forced clear storage (so this simulates returning visitor and benefits from static assets cache):

Lighthouse scores for Vite (navigation, mobile, without forced cache clear)... Click to enlarge...

 

vim.morzel.net - Questions With Explanations

TL;DR

Click here to start a Vim quizClick here to get a random question.

 

MY TOOLS

I've always been a fan of IDEs and have used quite a few: Delphi 2..7 as a hobbyist (nostalgia), Visual Studio 2003..2019 while working as a .NET developer and a bit of IntelliJ IDEA or Eclipse when doing Java...

Having a cohesive set of tools from feature-rich code editor to integrated debugger gives a great productivity boost. The only thing that bothered me was that the tools I was using were not fully keyboard-oriented...

So when I switched to Visual Studio Code for React projects (which sits somewhere between plain code editor and fully-fledged IDE), I thought it might be a good opportunity to use the mouse less by relying more on command line and to try Vim's modal way of editing with VSCodeVim plugin. People have varying opinions about this plugin but I think it's a great way to start taking advantage of the Normal mode while keeping all the amenities that VS Code provides (for example its support for React+TypeScript projects is hard to match).

After a while, I stated to feel the limits of emulator and decided to use Vim directly, hoping to stay productive thanks to ALE and it's support for tsserver and rust-analyzer. I've used vim-plug to easily manage a bunch of plugins (fzf.vim, lightline.vim, lightline-bufferlinevim-code-dark, vifm.vim, vim-commentary, vim-surround, vim-unimpaired...) and configured my .vimrc with mappings using spacebar as leader key (love it!)... I still notice some hiccups, like Vim getting confused what sort of comments to use in TSX code blocks, but I can honestly say that I'm a happy Vim user (especially combined with tmux and Nushell) and I want to use it more and get better at it.

I'm on Vim 9, but a lot of people enjoy Neovim, so you might give it a try. You can even go a step further and use something like LunarVim to get tones of features preconfigured for a more IDE-like experience...

 

SITE FOR LEARNING

I think the best way to learn something is to try to teach it, so I've built a questions and answers site that should help Vim users discover its useful features. The site has a random questions mode and a quiz mode where you can choose interesting topics. Perhaps in the future I will add some keyboard drills too. At the time of this writing (2022-10-03) the site has 128 questions so it covers just fraction of what Vim could do but I plan to add a couple of questions every week. 

 

vim.morzel.net screen... Click to enlarge...

 

Every single question has an explanation, and it's worth checking it as even the wrong answers might inform you about something you didn't know about Vim. I also often add a tip about alternative usage or hint if there are some important details in Vim's extensive help system. I intend to keep the questions unambiguous so the correct answer should not be a matter of personal preference.

The site is a learning resource, I have made no attempt to hide the correct answer from browser so you should get an instant feedback. Yes, you can cheat just by opening devtools but what's the point? There are no prizes :)

 

HELP NEEDED

Please tell me if you see any inaccuracy in a question or explanation (the question ID becomes visible after answering).

The site should adopt to various screen sizes (from small smartphone to desktop) and is expected to work on modern browsers (I am checking in Chrome, Firefox and Edge on desktop and in Chrome on mobile)... Let me know if you see a bug.

 

THANKS!

I use this project as a playground to test some things that might be useful for me at work or that are just interesting to play with. Shout out to the authors of these open source projects:

Oh, and if you find the site useful, please share a link to it somewhere on the Internet.

Filtering DOM Dump in React Testing Library

PRINTING DOM

Last post was about suppressing chosen console warnings while executing tests. This one is also about making tests output less noisy...

React Testing Library has a nice feature for outputting well formatted container/screen DOM when get or find queries (getByText, findByRole etc.) fail.

Document structure dump is limited to a value controlled by DEBUG_PRINT_LIMIT setting (default: 7000) but even smaller dumps can become inconvenient to use. That is especially true if part of the printed markup doesn't exactly belong to tested component but is somehow injected by external a piece of code. I've had such issue with a dependency that created a container with SVG icons, which completely flooded DOM dumps, making them practically useless. I can't post the actual code here (work stuff) but I've made a little app to simulate the issue.

 

THE PROBLEM

Let's say we have such component:

import React from 'react';
import './InjectLoremIpsum';

const MyComponent = () => (
    <div>
        Hello from MyComponent!
    </div>
);

export default MyComponent;

and we have a test (obviously failing):

test('checking if element filter works', () => {
    render(<MyComponent />);
    expect(screen.getByText('Random stuff that does not exist...')).toBeInTheDocument();
});

so we expect to see a nice error message along with screen's DOM dump containing MyComponent markup:

 FAIL  src/MyComponent.test.js
  ● checking if element filter works

    TestingLibraryElementError: Unable to find an element with the text: Random stuff that does not exist.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    Ignored nodes: comments, <script />, <style />
    <body>
      <div>
        <div>
          Hello from MyComponent!
        </div>
      </div>
    </body>

But there's a problem! MyComponent imports a module (InjectLoremIpsum) that injects elements to document body and this stuff gets printed too:

FAIL  src/MyComponent.test.js
  ● checking if element filter works

    TestingLibraryElementError: Unable to find an element with the text: Random stuff that does not exist.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    Ignored nodes: comments, <script />, <style />
    <body>
      <div
        id="injected-stuff"
      >
        <div
          style="display: none;"
        >
          <p>
            Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas euismod, purus eu mollis efficitur, tellus lectus laoreet leo, ut tincidunt magna felis id tortor. Donec ut mi feugiat, pellentesque libero id, auctor eros. Proin tincidunt tristique orci ac vehicula. Praesent consectetur auctor elit, eu faucibus neque placerat ut. Curabitur ullamcorper venenatis vulputate. Ut viverra at lectus et placerat. Ut efficitur sodales elit, quis faucibus dolor interdum vitae. Ut ultrices sapien vitae ligula laoreet, non facilisis tortor vulputate. Mauris non magna fringilla, placerat purus eget, accumsan sapien. Nunc sit amet elementum libero. Pellentesque et vulputate tellus.
          </p>
          <p>
            Nam ac est rhoncus felis bibendum mattis quis eget neque. Phasellus rhoncus et ex vitae mollis. Mauris ultrices non metus ac lacinia. Nulla ut porta nulla. Aliquam pellentesque euismod orci, sed gravida tortor vestibulum id. Nulla at lectus molestie, consectetur turpis quis, viverra massa. Aliquam ultricies, nulla id varius varius, lorem nibh aliquet odio, ac bibendum erat ante quis nisi. Aenean quis hendrerit justo, id ultricies mi. Donec volutpat lacus placerat nisl malesuada dictum. Pellentesque tincidunt dolor et arcu elementum, interdum volutpat erat vehicula.
          </p>
          <p>
            Vivamus ut ultrices odio, sed consectetur diam. Praesent pharetra et massa vel elementum. Quisque tortor massa, pharetra vitae sapien quis, ullamcorper auctor quam. Nam scelerisque molestie leo, a cursus urna. Praesent massa erat, ullamcorper ac arcu sed, tincidunt imperdiet dui. Nulla facilisi. Nulla erat augue, sollicitudin nec eros ut, rhoncus lacinia leo. Aliquam in sem massa. Sed dictum erat erat, ac finibus est pretium id. Proin sit amet odio sem. Nulla vehicula nulla viverra dignissim ornare. Phasellus vitae lectus vitae nisi condimentum facilisis. Nullam eu nulla tellus. Aliquam quis diam nec lectus volutpat consectetur et sit amet lacus. Nulla condimentum nibh quis quam vestibulum imperdiet.
          </p>
          <p>
            Suspendisse tristique quis eros vitae gravida. Quisque at lectus quam. Mauris aliquet turpis a erat aliquet auctor. Curabitur maximus, ipsum et accumsan blandit, urna dui scelerisque dui, at dapibus enim odio ut ligula. Vestibulum ligula sapien, fermentum id erat vitae, accumsan condimentum felis. Integer fringilla nibh in purus posuere scelerisque. Mauris et erat tortor.
          </p>
          <p>
            Praesent in cursus neque. Pellentesque feugiat egestas nibh, nec faucibus nisi lobortis vitae. Phasellus leo mauris, commodo eget urna condimentum, eleifend bibendum mi. Morbi blandit egestas cursus. Phasellus a elit consequat, euismod tortor ac, elementum est. Ut condimentum mattis mi, at blandit nisl vehicula in. Proin imperdiet ullamcorper ligula non consequat. Proin eu rhoncus nibh, eget tristique nulla. Fusce aliquet neque eu quam imperdiet scelerisque. Integer vitae consectetur diam. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi eget ligula at dolor pretium maximus. Integer sit amet ipsum id justo elementum dignissim non quis purus. Ut fringilla venenatis felis non volutpat. Praesent et massa in nibh posuere gravida vel non mauris.
          </p>
          <p>
            Phasellus auctor, ante sed egestas posuere, justo erat maximus ipsum, eget fringilla tortor nisl vitae nulla. Duis rutrum eros eget ante consectetur, ut blandit orci elementum. Nam maximus lacus sed elit elementum hendrerit. Phasellus lorem magna, hendrerit non pretium eget, scelerisque quis diam. Sed pulvinar quis felis at placerat. Quisque malesuada in est vitae viverra. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
          </p>
          <p>
            Fusce eget gravida lectus. Morbi fringilla ac dui sed varius. Suspendisse ac interdum massa, ac egestas enim. Donec non lacus eget elit efficitur ultrices. Vestibulum volutpat facilisis orci, id cursus nulla egestas ut. Aenean metus magna, rhoncus et lorem cursus, tempus aliquam nibh. Quisque non malesuada urna. Maecenas quis sodales odio. Cras tempor est velit, at laoreet augue aliquet a. Nullam scelerisque tincidunt elementum. Mauris felis odio, feugiat in nisl vel, maximus efficitur metus.
          </p>
          <p>
            Donec ut congue urna. Mauris sit amet facilisis nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Proin dapibus rhoncus feugiat. Praesent rutrum nisi lorem, eget dapibus ipsum pellentesque non. In varius pretium mauris. Donec vitae suscipit quam. Praesent ac dapibus eros. Donec fermentum, leo in fringilla sagittis, felis dui auctor augue, vitae porttitor nibh augue sed diam. Vivamus ac sem vitae risus placerat ultricies. Nulla lobortis consequat molestie. Donec dolor ipsum, ultrices in sagittis in, dictum vel tellus. Fusce volutpat id enim ut rutrum. Nullam sit amet justo commodo, cursus erat non, facilisis arcu. Ut porttitor ipsum sapien, id finibus nisl molestie a.
          </p>
          <p>
            Sed urna lectus, ornare non sagittis ac, ullamcorper vitae diam. Nunc quis placerat leo. Praesent rhoncus nulla nec ipsum dapibus porta. Aliquam vestibulum metus metus, ut sagittis sem bibendum at. Integer placerat arcu erat, at congue risus convallis eget. Nullam tincidunt eget lacus ut laoreet. Nam consequat luctus velit, non facilisis magna. Nunc facilisis, enim sed efficitur varius, turpis nulla laoreet sem, ut sagittis orci diam in augue. Nunc iaculis neque metus, vel dictum sapien elementum nec. Quisque ullamcorper sem sit amet dictum sollicitudin. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Morbi tincidunt fermentum iaculis. Nunc ullamcorper justo eget dignissim cursus. Donec sit amet est ex. Aliquam eget accumsan odio. Aenean eu iaculis lacus, egestas rhoncus lorem.
          </p>
          <p>
            Nunc in velit ac leo condimentum aliquam quis ut lorem. Sed sit amet pharetra metus. Nulla accumsan lorem ex, quis sodales felis mollis id. In venenatis tellus a magna tincidunt, a semper libero feugiat. Suspendisse at orci in elit vestibulum auctor. Ut mi nisl, tempor id scelerisque semper, viverra ac nisi. In quam risus, gravida tempor varius at, maximus id nisi. Nullam at ligula sapien. Nunc libero orci, dictum sit amet imperdiet non, ultricies et metus. Nunc vel elit imperdiet, volutpat dui venenatis, consequat arcu. Praesent in fringilla massa. Sed volutpat at turpis sit amet dapibus. Curabitur id nunc at libero tristique malesuada eget eget nulla. Phasellus eu nisi sem. Donec elementum ut velit vitae ultrices. Integer ullamcorper enim ac nisi sodales, pellentesque lobortis neque lacinia.
          </p>
        </div>
      </div>
      <div>
        <div>
          Hello from MyComponent!
        </div>
      </div>
    </body>

The div with "injected-stuff" id is there to simulate a piece of markup that gets included in the body and clutters the output of tested component. Like I mentioned, I've had a case when a crucial dependency created a container with very large amount of SVG elements. I couldn't just removed the dep, but I still wanted to have usable tests outputs...

 

THE SOLUTION

Fortunately RTL lets as define a custom implementation of getElementError function that returns errors for failing get* and find* calls. 

Here's an example config in setupTest.js file (standard file from CRA):

import '@testing-library/jest-dom';
import { configure, prettyDOM } from '@testing-library/dom';

configure({
    getElementError: (message, container) => {
        const customMessage = [
            message,
            prettyDOM(container, null, {
                filterNode: node => node.id !== 'injected-stuff'
            })
        ].join('\n\n');

        return new Error(customMessage);
    }
});

Notice the prettyDOM usage which provides filterNode function. If that function returns false, the node will not become a part of the pretty-printed DOM. We use it to filter out pesky injected-stuff div. With such configuration in place, the test output becomes usable again:

  FAIL  src/MyComponent.test.js
  ● checking if element filter works

    Unable to find an element with the text: Random stuff that does not exist.... This could be because the text is broken up by multiple elements. In this case, you can provide a function for your text matcher to make your matcher more flexible.

    <body>
      <div>
        <div>
          Hello from MyComponent!
        </div>
      </div>
    </body>

 

IT'S NOT PERFECT

One drawback of providing custom getElementError config is that a stack trace included at the end of error will point to a line with return statement in getElementError instead of a line with failing assertion. This is not a big deal in practice because failing test message should make it pretty clear what went wrong.

The other issue is that default error reporting skips comments and scripts/styles tags and our custom implementation doesn't do it. I wanted to keep the example as simple as possible but you could put arbitrarily complex logic inside filterNode function (check out RTL source for hints).

Oh, and if you see such message:

pretty-format: Unknown option "filterNode".

make sure you are using latest @testing-library/react version. My project originally had v11.2.7 and I got this error but after updating to v12.0.0 everything went ok.

 

SAMPLE PROJECT

GitHub repo: https://github.com/morzel85/blog-post-react-testing-library-element-filter (React 17.0.2).

Run npm install followed by npm test to see how the config in setupTest.js tweaks DOM printing behavior when RTL test fails.

Suppressing Legacy Lifecycle Method Warnings in Tests for Chosen React Components

LEGACY METHODS

In preparation for async rendering, React team decided to discourage component authors from relaying on these lifecycle methods: componentWillMountcomponentWillReceiveProps and componentWillUpdate (details).

If you need to use such methods you should prefix their names with UNSAFE_ (for example: UNSAFE_componentWillMount).

Methods without prefix might stop working in React 18.x (some docs are outdated and still say that the drop will happen in version 17)...

If React detects that a component uses legacy method it issues a warning (through call to console.warn) such as this:

Warning: componentWillReceiveProps has been renamed, and is not recommended for use. See https://reactjs.org/link/unsafe-component-lifecycles for details.
    
    * Move data fetching code or side effects to componentDidUpdate.
    * If you're updating state whenever props change, refactor your code to use memoization techniques or move it to static getDerivedStateFromProps. Learn more at: https://reactjs.org/link/derived-state
    * Rename componentWillReceiveProps to UNSAFE_componentWillReceiveProps to suppress this warning in non-strict mode. In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names, you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
    
    Please update the following components: LegacyComponent

 

REDUCING NOISE

It's a good thing that React warns about code that uses outdated methods. If you control the code that does so, you should upgrade the names (you can even do it automatically by codemod). But what if you don't? In such case, the warnings and stack traces will clutter output of test runs increasing a chance that more serious issue goes unnoticed.

So if you want/need to suppress the legacy method warnings for selected components, you could add such override to your setupTests.js file (assuming that you've started the app with CRA):

const originalConsoleWarn = console.warn;

// Override to suppress legacy lifecycle method warnings on CHOSEN components. 
console.warn = (...args) => {
    const [firstArg, secondArg] = args;

    // Here's a sample warning we want to skip (line breaks added to limit line length):

    /* ------------------------------------------------------------------------------------------------------- 
    Warning: componentWillMount has been renamed, and is not recommended for use.
    See https://reactjs.org/link/unsafe-component-lifecycles for details.
    
    * Move code with side effects to componentDidMount, and set initial state in the constructor.
    * Rename componentWillMount to UNSAFE_componentWillMount to suppress this warning in non-strict mode.
      In React 18.x, only the UNSAFE_ name will work. To rename all deprecated lifecycles to their new names,
      you can run `npx react-codemod rename-unsafe-lifecycles` in your project source folder.
    
    Please update the following components: AnotherLegacyComponent, LegacyComponent
    ------------------------------------------------------------------------------------------------------- */

    // Notice how the message ends with a list of components which have the legacy method. 
    // This list is provided to console.warn as a second argument which is used to fill %s placeholder
    // in the message passed as the first argument.

    // We want to suppress warnings only on chosen components instead of blindly skipping all unsafe 
    // lifecycle warnings. If warning contains component outside of the skip list, then the message 
    // should not be suppressed!
    const componentsToSkip = ['LegacyComponent', 'AnotherLegacyComponent'];

    const shouldSkip = 
        typeof firstArg === 'string' && // It could also be an object!
        firstArg.includes('In React 18.x, only the UNSAFE_ name will work.') &&
        typeof secondArg === 'string' && // It could also be an object!
        secondArg.split(',').every(name => componentsToSkip.includes(name.trim()));

    !shouldSkip && originalConsoleWarn(...args);
}

We could simply go for rejecting any warn message that contains a text about _UNSAFE methods but being more picky and skipping warnings only for known components is safer. This way if some new dependency with legacy methods appears in the app, we will know about it. 

I hope the comments in the code above make it clear how the override works. Key thing to note is that console.warn can take multiple arguments (which could be strings or objects) and that React uses second argument to to fill %s placeholder with component names (comma-separated).

Overriding console methods feels a bit hacky, but might be worthwhile if you would like to fail a CI build in case of any warnings... Hopefully one day all components will have upgraded methods names (or switch to function/hooks completely) and this trick will not be needed. A built-in React feature for suppressing such warnings on selected components would be nice too. If there is such thing, please let me know :) 

 

SAMPLE PROJECT

GitHub repo: https://github.com/morzel85/blog-post-react-test-console-warn-filter (React 17.0.2).

Run npm install followed by npm test to see how the override in setupTest.js suppress warnings for LegacyComponent.js (3 methods) and AnotherLegacyComponent.js (1 method).

WebGPU/WGSL Hello Triangle!

INTRO

WebGPU is and ongoing effort to make browsers even more powerful platforms. When vendors finish their work (2021/2012?), web apps will be able to leverage the awesome power of modern GPU to render graphics and do general-purpose computing through dedicated API!

High-end games, computer vision, CAD, video processing, machine learning... all done in open, secure and cross-platform way of the Web. Nice. 

Oh, and if you are wondering what's the reason for creating WebGPU while WebGL exists, please check these two articles:

 

HELLO TRIANGLE

After a bit of digging, I've noticed that some examples posted online no longer work (that's expected), use shading language other than WGSL or may be a bit too complex for a newcomer... So, I've created my own Hello Triangle example (based on austinEng work) that uses just one HTML file and one JS file.

Breaking changes are likely as the specs evolve, so please check the repo for updates.

The code renders properly and runs without warnings in Chrome Canary v91.0.4457.2 x64 Win 10 with --enable-unsafe-webgpu flag set. Tested on 2021-03-25.

Here are the aforementioned files:

index.html

  
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>WebGPU/WGSL Hello Triangle!</title>
</head>

<body>
    <canvas id="canvas" width="400" height="400"></canvas>   
    <script src="script.js"></script>
</body>

</html>

script.js

(async () => {
    if (!navigator.gpu) {
        alert('Your browser does not support WebGPU or it is not enabled. More info: https://webgpu.io');
        return;
    }

    const adapter = await navigator.gpu.requestAdapter();
    const device = await adapter.requestDevice();

    const canvas = document.getElementById('canvas')
    const context = canvas.getContext('gpupresent');

    const swapChainFormat = 'bgra8unorm';

    const swapChain = context.configureSwapChain({
        device,
        format: swapChainFormat
    });

    const vertexShaderWgslCode =
        `
        const pos : array<vec2<f32>, 3> = array<vec2<f32>, 3>(
        vec2<f32>(0.0, 0.5),
        vec2<f32>(-0.5, -0.5),
        vec2<f32>(0.5, -0.5));
      
        [[builtin(position)]] var<out> Position : vec4<f32>;
        [[builtin(vertex_idx)]] var<in> VertexIndex : i32;
      
        [[stage(vertex)]]
        fn main() -> void {
            Position = vec4<f32>(pos[VertexIndex], 0.0, 1.0);
            return;
        }
    `;

    const fragmentShaderWgslCode =
        `
        [[location(0)]] var<out> outColor : vec4<f32>;
      
        [[stage(fragment)]]
        fn main() -> void {
            outColor = vec4<f32>(0.0, 1.0, 0.0, 1.0);
            return;
        }
    `;

    const pipeline = device.createRenderPipeline({
        vertex: {
            module: device.createShaderModule({
                code: vertexShaderWgslCode
            }),
            entryPoint: 'main'
        },
        fragment: {
            module: device.createShaderModule({
                code: fragmentShaderWgslCode
            }),
            entryPoint: 'main',
            targets: [{
                format: swapChainFormat,
            }]
        },
        primitive: {
            topology: 'triangle-list',
        }
    });

    const commandEncoder = device.createCommandEncoder();
    const textureView = swapChain.getCurrentTexture().createView();

    const renderPassDescriptor = {
        colorAttachments: [{
            attachment: textureView,
            loadValue: { r: 0.5, g: 0.5, b: 0.5, a: 1.0 },
        }]
    };

    const passEncoder = commandEncoder.beginRenderPass(renderPassDescriptor);
    passEncoder.setPipeline(pipeline);
    passEncoder.draw(3, 1, 0, 0);
    passEncoder.endPass();

    device.queue.submit([commandEncoder.finish()]);
})();

The code uses WebGPU with WebGPU Shading Language to render a green triangle on a gray background inside a 400x400 px canvas. 

All this stuff to get a triangle? Don't worry :) Remember that WebGPU is a low-level API. Your favorite libraries like tree.js or babylon.js will use it internally.

 

WHY WGSL?

There has been a bit of controversy surrounding the choice of creating WGSL instead of going for widely supported GLSL... You might want to check this issue and this comment in particular (from one of the main contributors to WebGPU) to see the rationale.