Skip to content

6.0.0 Preparing for breaking changes

Jimmy Somsanith edited this page Sep 9, 2020 · 4 revisions

TL;DR

Breaking changes are coming in 6.0. They will impact how components/sub-components/functions/hooks are exposed.

  • use @talend/scripts-core to ease the process.
  • if your app don't import from specific nested files directly, this will go smoothly.
  • otherwise, you will have to get those entities from their main component (ex: List.hooks, List.Manager).

Table of content

Introduction.

Current status

Target: CDN

Conclusion

Introduction

@talend/ui has been growing a lot since the last 3 years. We plan to introduce some major breaking changes to clean up what we expose and how we expose the components and utility functions.

The goal is not just to clean things, those changes are needed to bring our library to the next level:

Serving it via CDN

TL;DR

Current status

To understand what we target we need to answer to those questions:

  • how do we build the packages ?
  • what are exposed ?
  • how do we use @talend/ui in the apps ?

How we build the packages

We use babel to compile the code to be supported in all the target browser. That allows us to use new ES features.

This step keeps the folder hierarchy.

Source folder hierarchy Compiled folder hierarchy
/src
    |_ /AboutDialog
        |_ AboutDialog.scss
        |_ AboutDialog.component.js
        |_ AboutDialog.test.js
        |_ AboutDialogTable.component.js
        |_ AboutModal.stories.js
        |_ index.js
    |_ /ActionBar
    |_ /ActionIntercom
/lib
    |_ /AboutDialog
        |_ AboutDialog.scss
        |_ AboutDialog.component.js
        |_ AboutDialog.component.js.map
        |_ AboutDialogTable.component.js
        |_ AboutDialogTable.component.js.map
        |_ index.js
        |_ index.js.map
    |_ /ActionBar
    |_ /ActionIntercom

What are exposed

We have an index.js at the root of the app that exposes almost all the components, some constants, some functions. That is quite a mess to be honest.

In each folder, we also have an index.js that exposes the main component as default, and sometimes some sub components, functions, or hooks. The sub components and hooks can be useful when we allow composition for example.

How do we use @talend/ui in the apps

In Talend apps, we usually import components/constants/functions/hooks from folder index

import AboutDialog from '@talend/react-components/lib/AboutDialog`;
import List, { hooks as listHooks } from '@talend/react-components/lib/List/ListComposition';

Importing from /lib/<component_folder> allows us to tree shake the library, including only the components we use in our app bundle.

But we are in javascript, and every export from a file, can be imported :/

File Javascript possible import
/src/index.js import { List } from '@talend/react-components'
/src/List/index.js import List from '@talend/react-components/lib/List'
/src/List/ListComposition/index.js import List from '@talend/react-components/lib/List/ListComposition'
/src/List/ListComposition/index.js import { hooks } from '@talend/react-components/lib/List/ListComposition'
/src/List/ListComposition/Manager/hooks/useCollectionSelection.js import useCollectionSelection from '@talend/react-components/lib/List/ListComposition/Manager/hooks/useCollectionSelection.js'

ℹ️ There are cases where some apps do some nested imports, and that introduces part of the issues to enable @talend/ui on CDN.


Target CDN

We want to serve @talend/ui on a CDN. This allows:

  • to push some common changes at the same time such as a change of color in a component
  • no need to upgrade and release all the apps
  • better caching
  • to download several bundles in parallel

Of course there are some drawbacks

  • no tree-shaking anymore
  • we download the whole library

But the parallel download and the caching saves us a bit.

To understand the issue, let's see how we can serve a library on a CDN

UMD format

When serving a library via CDN, we set 1 file for js, containing the whole library. There is no split.

Some example

<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<script crossorigin src="https://cdn.jsdelivr.net/npm/lodash@4.17.20/lodash.js"></script>

⚠️ Nested imports don't work anymore because there is no folder hierarchy ! We can only import from index.


How to fix main components imports

We need to expose the main components directly in src/index.js.

import { List } from '@talend/react-components'; // this is ok

-import List from '@talend/react-components/lib/List';
+import { List } from '@talend/react-components';

-import List from '@talend/react-components/lib/List/ListComposition';
+import { ListComposition } from '@talend/react-components'; // we need to expose it in index

But we got you covered. We are preparing a babel plugin to change those nested main components imports into an import from index. As you use @talend/scripts-core, this will be integrated directly, no need to change those imports manually.


ℹ️ Use @talend/scripts-core to support this for almost free


How to fix composition sub components imports

Let's take an example: the ListComposition components.

import ListManager from '@talend/react-components/lib/List/ListComposition/Manager';
import ListToolbar from '@talend/react-components/lib/List/ListComposition/Toolbar';
import VList from '@talend/react-components/lib/List/ListComposition/VList';

function MyList() {
    return (
        <ListManager id="my-list" collection={simpleCollection}>
            <ListToolbar>
                {...actions}
            </ListToolbar>
            <VList>
                {..columnDefinitions}
            </VList>
        </ListManager>
    );
}

In List composition, we expose sub components to compose the List, including a toolbar and the real list of data.

A fast way to allow UMD is to expose all the components in the main index.js. But that would mean

  • to rename lots of components to add a context in the name. For example VList doesn't fit anymore, we need to enforce the fact that it's used in the list composition.
  • the index.js will expose tons of items.

A better way to do that is to attach all sub components on a main component.

-import ListManager from '@talend/react-components/lib/List/ListComposition/Manager';
-import ListToolbar from '@talend/react-components/lib/List/ListComposition/Toolbar';
-import VList from '@talend/react-components/lib/List/ListComposition/VList';
+import { ListComposition as List } from '@talend/react-components';

-<ListManager>
+<List.Manager>

-<ListToolbar>
+<List.Toolbar>

-<VList>
+<List.VList>

So we keep main components in index.js, and all sub components are exposed via the main one.

import { ListComposition as List } from '@talend/react-components';

function MyList() {
    return (
        <List.Manager id="my-list" collection={simpleCollection}>
            <List.Toolbar>
                {...actions}
            </List.Toolbar>
            <List.VList>
                {..columnDefinitions}
            </List.List>
        </List.Manager>
    );
}

⚠️ This is a breaking change. We will change how sub components are exposed.


Unfortunately, we can't automate this change, and the time spent to do a codemod doesn't worth it. We will have to adapt manually. But luckily, there are not that many of those cases today.

How to fix functions and hooks imports

This case is similar to sub components. Utility functions and hooks are created for a component need, we will attach them to the main component.

-import useCollectionSelection from '@talend/react-components/lib/List/ListComposition/Manager/hooks/useCollectionSelection.hook';

+import { ListComposition as List } from '@talend/react-components`;
+const { useCollectionSelection } = List.hooks;

⚠️ This is a breaking change. We will change how utility functions and hooks are exposed.


Conclusion

Breaking changes are coming in 6.0. They will impact how components/sub-components/functions/hooks are exposed.

  • use @talend/scripts-core to ease the process.
  • if your app don't import from specific nested files directly, this will go smoothly.
  • otherwise, you will have to get those entities from their main component (ex: List.hooks, List.Manager).