coder-xiaotian / swc-useclient

A swc plugin that automatically converts React component libraries into "React Client Component". For example, you can automatically convert components from @mui into "React Client Component" without having to wrap a component that uses "use client".
MIT License
15 stars 0 forks source link

Plugin does not work from nextjs v14.1.2 and onwards #4

Open arnoBruynseels opened 5 months ago

arnoBruynseels commented 5 months ago

The plugin works perfectly on nextjs v14.1.1 but once you install nextjs v14.1.2 everything breaks.

Nextjs complains that I need to use the 'use client' directive for the external component library I added to the plugin config.

Any fixes?

coder-xiaotian commented 5 months ago

The plugin works perfectly on nextjs v14.1.1 but once you install nextjs v14.1.2 everything breaks.

Nextjs complains that I need to use the 'use client' directive for the external component library I added to the plugin config.

Any fixes?

I did not reproduce this issue in my repo used nextjsv14.1.4. Please provide your code to better reproduce the issue.

arnoBruynseels commented 5 months ago

The plugin works perfectly on nextjs v14.1.1 but once you install nextjs v14.1.2 everything breaks. Nextjs complains that I need to use the 'use client' directive for the external component library I added to the plugin config. Any fixes?

I did not reproduce this issue in my repo used nextjsv14.1.4. Please provide your code to better reproduce the issue.

In your provided repo you don't have any swcPlugins defined.

This is how we use the plugin:

image

coder-xiaotian commented 5 months ago

The plugin works perfectly on nextjs v14.1.1 but once you install nextjs v14.1.2 everything breaks. Nextjs complains that I need to use the 'use client' directive for the external component library I added to the plugin config. Any fixes?

I did not reproduce this issue in my repo used nextjsv14.1.4. Please provide your code to better reproduce the issue.

In your provided repo you don't have any swcPlugins defined.

This is how we use the plugin:

image

I can start success in my repo with antd. I think it's probably an issue with the directory structure of your component library. The export directory structure of antd is as follows:

image

I can't find @spot/ui-components in npm. Please provide more information about it. And you can provide the error logs.

tjones4701 commented 5 months ago

Hey, this no longer works with next 14.2.0, any chance of a fix for this? : failed to run Wasm plugin transform. Please ensure the version of swc_core used by the plugin is compatible with the host runtime.

coder-xiaotian commented 5 months ago

Hey, this no longer works with next 14.2.0, any chance of a fix for this? : failed to run Wasm plugin transform. Please ensure the version of swc_core used by the plugin is compatible with the host runtime.

I get it. What is your OS version useing.

coder-xiaotian commented 5 months ago

Hey, this no longer works with next 14.2.0, any chance of a fix for this?嘿,这不再适用于下一个 14.2.0,有修复这个问题的机会吗? : failed to run Wasm plugin transform. Please ensure the version of swc_core used by the plugin is compatible with the host runtime.:无法运行 Wasm 插件转换。请确保插件使用的 swc_core 版本与主机运行时兼容。

I fixed it. You can update use-client to 1.2.0.

arnoBruynseels commented 3 weeks ago

Hi it's me again.

I'm trying to up our Nextjs version from 14.1.1 to to lastest (14.2.6).

Steps I took:

  1. Install Nextjs version 14.2.6
  2. Install use-client version 1.2.0
  3. Applied following config:

image

NOTE: all the @spot packages are private internal repositories. We create our own component library. This component library is build using React and Vite where all components are exported in an index.js file

Below is what it looks like:

image

  1. Run the Nextjs application
  2. Following error occurs:

image

Where does it go wrong? Did we implement or use this package wrong? Did we implement or expose our UI library the wrong way?

Please help, we can't seem to find the source of the problem.

Thanks!

coder-xiaotian commented 2 weeks ago

Hi it's me again.

I'm trying to up our Nextjs version from 14.1.1 to to lastest (14.2.6).

Steps I took:

  1. Install Nextjs version 14.2.6
  2. Install use-client version 1.2.0
  3. Applied following config:

image

NOTE: all the @spot packages are private internal repositories. We create our own component library. This component library is build using React and Vite where all components are exported in an index.js file

Below is what it looks like:

image

  1. Run the Nextjs application
  2. Following error occurs:

image

Where does it go wrong? Did we implement or use this package wrong? Did we implement or expose our UI library the wrong way?

Please help, we can't seem to find the source of the problem.

Thanks!

Please provide the [local]/users page code and this file code.

image
arnoBruynseels commented 2 weeks ago

``> > Hi it's me again.

I'm trying to up our Nextjs version from 14.1.1 to to lastest (14.2.6). Steps I took:

  1. Install Nextjs version 14.2.6
  2. Install use-client version 1.2.0
  3. Applied following config:

image NOTE: all the @spot packages are private internal repositories. We create our own component library. This component library is build using React and Vite where all components are exported in an index.js file Below is what it looks like: image

  1. Run the Nextjs application
  2. Following error occurs:

image Where does it go wrong? Did we implement or use this package wrong? Did we implement or expose our UI library the wrong way? Please help, we can't seem to find the source of the problem. Thanks!

Please provide the [local]/users page code and this file code. image

[local]/users page code:

image

UserPageContent code:

image

'use client';

import { AgGrid, BooleanText, agGridCssClasses } from '@spot/ui-components';
import type { AgGridProps } from '@spot/ui-components';
import type { ICellRendererParams, RowClassParams } from 'ag-grid-community';
import { useTranslations } from 'next-intl';
import type { ReactNode } from 'react';
import { useCallback, useMemo } from 'react';
import { useState } from 'react';

import UserModalContent from '@/app/[locale]/users/_components/users-page-content/components/user-modal-content/UserModalContent';
import UserModalFooter from '@/app/[locale]/users/_components/users-page-content/components/user-modal-footer/UserModalFooter';
import type { UserModalContentTabKey } from '@/app/[locale]/users/_components/users-page-content/components/user-modal-header/UserModalHeader';
import UserModalHeader from '@/app/[locale]/users/_components/users-page-content/components/user-modal-header/UserModalHeader';
import type { User } from '@/generated/graphql/types';
import { useSelector } from '@/lib/redux/redux-store';
import { selectUsersData } from '@/lib/redux/slices/user-management/selectors';

const UsersPageContent = () => {
  const t = useTranslations();

  const users = useSelector(selectUsersData);

  const [modalActiveTabKey, setModalActiveTabKey] = useState<UserModalContentTabKey>('generalTab');

  const modalTitle = useCallback(
    (selectedRecord: unknown): ReactNode => {
      return (
        <UserModalHeader
          user={selectedRecord as User}
          selectedTabKey={modalActiveTabKey}
          setSelectedTabKey={setModalActiveTabKey}
        />
      );
    },
    [modalActiveTabKey],
  );

  const modalContent = useCallback(
    (selectedRecord: unknown): ReactNode => {
      return <UserModalContent user={selectedRecord as User} selectedTabKey={modalActiveTabKey} />;
    },
    [modalActiveTabKey],
  );

  const modalFooter = useCallback(
    (selectedRecord: unknown): ReactNode => {
      return <UserModalFooter isCopyVisible={modalActiveTabKey === 'debugTab'} user={selectedRecord as User} />;
    },
    [modalActiveTabKey],
  );

  const columnDefs: AgGridProps['columnDefs'] = useMemo(() => {
    return [
      {
        headerName: t('application-common.user'),
        children: [
          {
            field: 'user',
            headerName: t('application-common.userID'),
          },
          {
            field: 'firstName',
            headerName: t('application-common.firstName'),
          },
          {
            field: 'lastName',
            headerName: t('application-common.lastName'),
          },
          {
            field: 'email',
            headerName: t('application-common.email'),
          },
          {
            field: 'department',
            headerName: t('application-common.department'),
          },

          {
            field: 'organisation',
            headerName: t('application-common.organisation'),
          },
        ],
      },
      {
        headerName: t('application-common.privileges'),
        children: [
          {
            field: 'hasAccess',
            headerName: t('application-common.access'),
            cellRenderer: (params: ICellRendererParams) => {
              return (
                <BooleanText
                  value={params.value}
                  variant={params.value ? 'success' : 'danger'}
                  trueText="Normal"
                  falseText="Blocked"
                />
              );
            },
            filterValueGetter: (params) => {
              return params.data.hasAccess ? t('application-common.normal') : t('application-common.blocked');
            },
          },
          {
            field: 'isAdmin',
            headerName: t('application-common.admin'),
            cellRenderer: (params: ICellRendererParams) => {
              return <BooleanText value={params.value} variant={params.value ? 'success' : 'danger'} />;
            },
            filterValueGetter: (params) => {
              return params.data.isAdmin ? t('application-common.yes') : t('application-common.no');
            },
            minWidth: 115,
            columnGroupShow: 'open',
          },
          {
            field: 'isWyreUser',
            headerName: t('application-common.wyreUser'),
            cellRenderer: (params: ICellRendererParams) => {
              return <BooleanText value={params.value} variant={params.value ? 'success' : 'danger'} />;
            },
            filterValueGetter: (params) => {
              return params.data.isWyreUser ? t('application-common.yes') : t('application-common.no');
            },
            minWidth: 115,
            columnGroupShow: 'open',
          },
          {
            field: 'isBusinessExpert',
            headerName: t('application-common.businessExpert'),
            cellRenderer: (params: ICellRendererParams) => {
              return <BooleanText value={params.value} variant={params.value ? 'success' : 'danger'} />;
            },
            filterValueGetter: (params) => {
              return params.data.isBusinessExpert ? t('application-common.yes') : t('application-common.no');
            },
            minWidth: 115,
            columnGroupShow: 'open',
          },
        ],
      },
      {
        headerName: t('application-common.usage'),
        children: [
          {
            field: 'lastSeen',
            headerName: t('application-common.lastName'),
          },
          {
            field: 'firstSeen',
            headerName: t('application-common.firstSeen'),
            minWidth: 115,
            columnGroupShow: 'open',
          },
        ],
      },
    ];
  }, [t]);

  const getRowClass: AgGridProps['getRowClass'] = useCallback((params: RowClassParams) => {
    return params.data.hasAccess === false ? agGridCssClasses.DangerBackground : '';
  }, []);

  const defaultColDef: AgGridProps['defaultColDef'] = useMemo(() => {
    return {
      sortable: true,
      resizable: true,
      filter: true,
    };
  }, []);

  const onCloseLogic: AgGridProps['onCloseLogic'] = useCallback(() => {
    setModalActiveTabKey('generalTab');
  }, []);

  const dataViewModalConfig: AgGridProps['dataViewModalConfig'] = useMemo(() => {
    return {
      hasSlideTabs: true,
      title: modalTitle,
      customContent: modalContent,
      customFooter: modalFooter,
    };
  }, [modalContent, modalFooter, modalTitle]);

  const columnsState: AgGridProps['columnsState'] = useMemo(() => {
    return [
      {
        colId: 'lastSeen',
        sort: 'desc',
      },
    ];
  }, []);

  return (
    <AgGrid
      showRecordCount
      enableGlobalFilter
      enableDataViewModal
      rowData={users}
      columnDefs={columnDefs}
      getRowClass={getRowClass}
      defaultColDef={defaultColDef}
      onCloseLogic={onCloseLogic}
      dataViewModalConfig={dataViewModalConfig}
      columnsState={columnsState}
    />
  );
};

export default UsersPageContent;

TimestampRefreshButton code:

Transpiled & Compiled:

image

Actual Component code:

import type { ButtonProps } from '@lib/inputs/button/Button';
import Button from '@lib/inputs/button/Button';
import Icon from '@lib/utilities/icon/Icon';
import IntervalIndication from '@lib/utilities/timestamp-refresh-button/components/interval-indication/IntervalIndication';
import TimeIndication from '@lib/utilities/timestamp-refresh-button/components/time-indication/TimeIndication';
import clsx from 'clsx';
import { addWeeks, isAfter, isSameDay, isSameWeek, isTomorrow, isYesterday } from 'date-fns';
import type { Moment } from 'moment';
import type { CSSProperties, PropsWithChildren } from 'react';

import styles from './TimestampRefreshButton.module.css';

export interface TimestampRefreshButtonProps {
  /**
   * Optional styling
   */
  style?: CSSProperties;
  /**
   * Optional className
   */
  className?: string;
  /**
   * Optional buttonText
   */
  buttonText?: string;
  /**
   * Optional plainDateFormat
   */
  plainDateFormat?: boolean;
  /**
   * Optional intervalSeparator
   */
  intervalSeparator?: string;
  /**
   * Optional onClick
   */
  handleClick: ButtonProps['handleClick'];
  /**
   * Optional time
   */
  time?: Date | Moment | null;
  /**
   * Optional timeFormat
   */
  timeFormat?: string;
  /**
   * Optional timeTo
   */
  timeTo?: Date | Moment;
  /**
   * Optional timeToFormat
   */
  timeToFormat?: string;
}

/**
 * TimestampRefreshButton component
 */
const TimestampRefreshButton = ({
  style,
  className,
  buttonText = 'Refresh',
  plainDateFormat = false,
  intervalSeparator = ' ... ',
  handleClick,
  time = new Date(),
  timeFormat = 'yyyy-MM-dd HH:mm',
  timeTo,
  timeToFormat = 'yyyy-MM-dd HH:mm',
}: PropsWithChildren<TimestampRefreshButtonProps>) => {
  let validTime: Date | null = null;

  if (time) {
    validTime = time instanceof Date ? time : time.toDate();
  }

  const today = new Date();

  let validTimeTo = timeTo;

  let relativeTimeFormat = 'yyyy-MM-dd HH:mm';

  if (timeTo) {
    validTimeTo = timeTo instanceof Date ? timeTo : timeTo.toDate();
  }

  if (!plainDateFormat && validTime !== null) {
    if (isSameDay(today, validTime)) {
      relativeTimeFormat = "'Today' HH:mm";
    } else if (isTomorrow(validTime)) {
      relativeTimeFormat = "'Tomorrow' HH:mm";
    } else if (isSameWeek(addWeeks(today, 1), validTime)) {
      relativeTimeFormat = 'yyyy-MM-dd HH:mm';
    } else if (isSameWeek(addWeeks(today, -1), validTime)) {
      relativeTimeFormat = 'yyyy-MM-dd HH:mm';
    } else if (isYesterday(validTime)) {
      relativeTimeFormat = "'Yesterday' HH:mm";
    } else if (isAfter(validTime, addWeeks(today, 1))) {
      relativeTimeFormat = 'yyyy-MM-dd HH:mm';
    }
  }

  const renderTime = () => {
    if (validTime === null) {
      return null;
    }

    if (timeTo) {
      return (
        <IntervalIndication
          time={validTime}
          timeFormat={timeFormat}
          timeTo={validTimeTo as Date}
          timeToFormat={timeToFormat}
          intervalSeparator={intervalSeparator}
          plainDateFormat={plainDateFormat}
          relativeTimeFormat={relativeTimeFormat}
        />
      );
    }

    return (
      <TimeIndication
        time={validTime}
        timeFormat={timeFormat}
        plainDateFormat={plainDateFormat}
        relativeTimeFormat={relativeTimeFormat}
      />
    );
  };

  return (
    <div className={clsx(className, [styles.timestampRefreshButton])} style={style}>
      {renderTime()}
      <Button handleClick={handleClick} variant="success" size="extra-small" className={styles.refreshButton}>
        <Icon icon="cached" size={15} className={styles.refreshIcon} />
        {buttonText}
      </Button>
    </div>
  );
};

export default TimestampRefreshButton;
coder-xiaotian commented 2 weeks ago

@arnoBruynseels You can try these two ways.

  1. add "@lib/" to config like this include: ["@lib/", ...];
  2. you can change change the import way from @spot/ui-components, for example change import { AgGrid } from '@spot/ui-components' to import AgGrid from '@spot/ui-components/your-component-path'.
arnoBruynseels commented 2 weeks ago

@arnoBruynseels You can try these two ways.

  1. add "@lib/" to config like this include: ["@lib/", ...];
  2. you can change change the import way from @spot/ui-components, for example change import { AgGrid } from '@spot/ui-components' to import AgGrid from '@spot/ui-components/your-component-path'.

Thanks, I will try this and keep you posted!