npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

@enonic/nextjs-adapter

v3.0.2

Published

> NextJS adapter with Guillotine support and basic views

Downloads

444

Readme

Enonic NextJS Adapter

NextJS adapter with Guillotine support and basic views

Installation

npm i --save @enonic/nextjs-adapter

Usage

All functions and views are split into 4 categories by usage:

  • @enonic/nextjs-adapter -- can be used both on server and client sides
  • @enonic/nextjs-adapter/server -- can only be used on server side
  • @enonic/nextjs-adapter/client -- can only be used on client side
  • @enonic/nextjs-adapter/views/... -- views folder, every view has its own file with a default export

Here's the usage example in your project:

// server-side functions are accessible at /server
import {fetchContent} from "@enonic/nextjs-adapter/server";

// views are placed in a folder called `views` and are default exports in files
import MainView from "@enonic/nextjs-adapter/views/MainView"

// ...
const props = await fetchContent(path, {
    contentPath: '/content/path',
    locale: 'en',
});
// ...

return <MainView {...props}/>

API

Server-side only functions

They are available at @enonic/nextjs-adapter/server

fetchContent(contentPath: string | string[], context: Context) => Promise<FetchContentResult>

Fetches the content/component by path with component queries optimization, page structure as well as runtime info calculation. This is the main method for querying content.

| Argument | Description | |-----------|-------------------| | path | Path to content | | context | Execution context |

Usage:

import {fetchContent} from '@enonic/nextjs-adapter/server';

const response = fetchContent('/path/to/content', context);

Response type:

type FetchContentResult = {
    error?: {
        code: string,
        message: string
    } | null;
    data: Record<string, any> | null,       // Result of the content query
    common: Record<string, any> | null,     // Result of the common query
    meta: MetaData,                         // Runtime information
    page: PageComponent | null,             // The structure of the page (not present when rendering single component)
};

fetchGuillotine(apiUrl: string, mapping: LocaleMapping, options?: FetchOptions) => Promise<GuillotineResult>

Makes a custom request to Guillotine. Used by fetchContent() method.

| Argument | Description | |-----------|---------------------------------------------------| | apiUrl | Guillotine API URL | | mapping | Mapping locale to project | | options | Request and Next.js config options (Optional) |

Usage:

import {fetchGuillotine} from '@enonic/nextjs-adapter/server';
import {getLocaleMapping} from '@enonic/nextjs-adapter';

const apiUrl = 'http://domain:8000/graphql/api';

const mapping = getLocaleMapping({
    contentPath: '/current/content/path',
});

const body = {
    query: 'qraphql query as string',
    variables: {
        foo: 'bar',
    },
};

const headers = {
    'custom-header': 'header-value',
};

const opts = {
    body,
    headers,
    method: 'GET',
    next: {
        revalidate: 60,
        tags: ['tag1', 'tag2'],
    }
};

const result = fetchGuillotine(apiUrl, mapping, opts);

Response type:

type GuillotineResult = {
    error?: {
        code: string,
        message: string
    } | null;
    [dataKey: string]: any;
};

fetchFromApi(apiUrl: string, mapping: LocaleMapping, options?: FetchOptions) => Promise<GuillotineResponseJson>

Makes custom third-party service request. Used by the fetchGuillotine() method.

| Argument | Description | |-----------|---------------------------------------------------| | apiUrl | Service API URL | | mapping | Mapping locale to project | | options | Request and Next.js config options (Optional) |

Usage:

import {fetchFromApi} from '@enonic/nextjs-adapter/server';
import {getLocaleMapping} from '@enonic/nextjs-adapter';

const apiUrl = 'http://domain:8000/graphql/api';

const mapping = getLocaleMapping({
    contentPath: '/current/content/path',
});

const body = {
    query: 'qraphql query as string',
    variables: {
        foo: 'bar',
    },
}

const headers = {
    'custom-header': 'header-value',
}

const opts = {
    body,
    headers,
    method: 'POST',
    next: {
        revalidate: 60,
        tags: ['tag1', 'tag2'],
    }
};

const result = fetchFromApi(apiUrl, mapping, opts);

fetchContentPathsForAllLocales(path: string, query?: string, count?: number) => Promise<ContentPathItem[]>

Loads all content paths for all locales. Generally used for static site generation.

| Argument | Description | |----------|---------------------------------------------------| | apiUrl | Service API URL | | query | Request and Next.js config options (Optional) * | | count | Max result count (Optional, defaults to 999) |

* Default query gets up to count results, sorts them by modifiedTime and excludes following content types:

"base:shortcut", 
"portal:fragment", 
"portal:template-folder", 
"portal:page-template", 
"media:*"

Usage:

import {fetchContentPathsForAllLocales} from '@enonic/nextjs-adapter/server';

const contentPaths = fetchContentPathsForLocale('/', 'custom graphql query', 1001);

fetchContentPathsForLocale(path: string, mapping: LocaleMapping, query?: string, count?: number) => Promise<ContentPathItem[]>

Loads all content paths for the current locale. Generally used for static site generation. Used by fetchContentPathsForAllLocales()

| Argument | Description | |-----------|-----------------------------------------------------------------------------------------------------| | apiUrl | Service API URL | | mapping | Mapping locale to project | | query | Request and Next.js config options (Optional, default value here) | | count | Max result count (Optional, defaults to 999) |

Usage:

import {fetchContentPathsForLocale} from '@enonic/nextjs-adapter/server';
import {getLocaleMapping} from '@enonic/nextjs-adapter';

const mapping = getLocaleMapping({
    contentPath: '/current/content/path',
});

const contentPaths = fetchContentPathsForLocale('/', mapping, 'custom graphql query', 1001);

Client-side only functions

They are available at @enonic/nextjs-adapter/client

<LocaleContextProvider locale="en">

Create a React.js context that allows child elements to access and modify current locale as well as localize static texts for current locale. See useLocaleContext() method for example.

| Argument | Type | Description | |----------|----------|------------------------------| | locale | String | Set the initial locale value |

Usage:

import {LocaleContextProvider} from '@enonic/nextjs-adapter/client';

<LocaleContextProvider locale="en"> ...child elements... </LocaleContextProvider>

useLocaleContext() => LocaleContextType

Methods to access and modify current locale as well as localize static texts, where LocaleContextType is:

interface LocaleContextType {
    dictionary: Dict
    locale: string
    localize: (key: string, ...args: any[]) => string
    setLocale: (locale: string) => Promise<Dict>
}

Usage:

'use client';

import {useLocaleContext} from '@enonic/nextjs-adapter/client';

export default function ClientSideComponent() {
    const {locale, localize} = useLocaleContext();
    const localizedText = localize('text.key');
    // ...
}

Both client and server-side functions

They are available at @enonic/nextjs-adapter

getUrl(url: string, meta: MetaData) => string

Converts a site-relative or absolute URL to relative one for current viewer (Next.js/Enonic XP). Also takes care of locale if needed.

INFO: For your URLs to work both in Enonic XP and Next.js you need to:

  1. Query site-relative or absolute URLs from guillotine
  2. Wrap them with getUrl() function in the views

| Argument | Description | |----------|---------------------------------------------------------| | url | URL you want to transform | | meta | Runtime data returned by fetchContent |

Usage:

import {getUrl} from '@enonic/nextjs-adapter';

const urlRelativeToViewer = getUrl('/some/content/url', meta);

getAsset(url: string, meta: MetaData) => string

Converts a local asset URL to relative one for current viewer (Next.js/Enonic XP). It doesn't append locales unlike [getUrl()] (#get-url).

INFO: For your URLs to work both in Enonic XP and Next.js you need to:

  1. Use relative URL to local asset
  2. Wrap them with getAsset() function in the views

| Argument | Description | |----------|---------------------------------------------------------| | url | asset URL you want to transform | | meta | Runtime data returned by fetchContent |

Usage:

import {getAsset} from '@enonic/nextjs-adapter';

const urlRelativeToViewer = getAsset('/some/asset/url', meta);

richTextQuery(fieldName: string) => string

This is a utility function for querying for RichTextData needed for RichTextView. It creates a graphql query string for HTML area input type with given field name.

| Argument | Description | |-------------|----------------------| | fieldName | HTML area field name |

Usage:

import {richTextQuery} from '@enonic/nextjs-adapter';

const query = `query($path:ID!){
    guillotine {
        get(key:$path) {
            _path
            type
            ${richTextQuery('htmlField')}
        }
    }
}`;

validateData(props: FetchContentResult) => void

Validates data returned by fetchContent() method. Throws an error or notFound() if data is invalid.

| Argument | Description | |----------|-----------------------------| | props | FetchContentResult object |

Usage:

import {fetchContent} from '@enonic/nextjs-adapter/server';
import {validateData} from '@enonic/nextjs-adapter';

const data = fetchContent({
    contentPath: '/path/to/content',
    locale: 'en',
});

validateData(data);

ComponentRegistry

Registry containing definitions of all components (i.e. pages, parts, layouts, macros, etc. ). It is used in the runtime by nextjs-adapter to make component queries and render components. It has several public methods:

static setCommonQuery(query: SelectedQueryMaybeVariablesFunc): void

Sets up a common query that is going to be executed along with component queries and passed to every component on the page.

| Argument | Description | |----------|---------------------------------------| | query | Common query definition |

Usage:

import {ComponentRegistry} from '@enonic/nextjs-adapter';

const query = {
    query: 'graphql query',
    variables: (path, context, config) => {
        return {
            path: path + '/some/processing'
        };
    }
}

ComponentRegistry.setCommonQuery(query);

static getCommonQuery(): SelectedQueryMaybeVariablesFunc

Gets the common query definition.

Response type:

type SelectedQueryMaybeVariablesFunc =
    string |
    QueryGetter |
    {
        query: string | QueryGetter,
        variables: VariablesGetter
    } |
    [string | QueryGetter, VariablesGetter];

Usage:

import {ComponentRegistry} from '@enonic/nextjs-adapter';

const query = ComponentRegistry.getCommonQuery();

static getByComponent(component: PageComponent): ComponentDefinition | undefined

Gets component definition from the ComponentRegistry.

| Argument | Description | |-------------|--------------------------------------| | component | Page component to get definition for |

Usage:

import {ComponentRegistry} from '@enonic/nextjs-adapter';

const definition = ComponentRegistry.getByComponent(component);

Response type:

interface ComponentDefinition {
    catchAll?: boolean; // set automatically depending on the binding
    query?: SelectedQueryMaybeVariablesFunc,
    configQuery?: string,
    processor?: DataProcessor,
    view?: React.FunctionComponent<any>
}

static addMacro(name: string, obj: ComponentDefinition): void

static addPart(name: string, obj: ComponentDefinition): void

static addLayout(name: string, obj: ComponentDefinition): void

static addCPage(name: string, obj: ComponentDefinition): void

static addContentType(name: string, obj: ComponentDefinition): void

static addComponent(name: string, obj: ComponentDefinition): void

Saves the component definition in ComponentRegistry by name.

NOTE: addComponent is used for defining general types of Enonic XP components by nextjs-adapter so you don't need to do it manually. Overriding default setup may break Enonic XP integration!

| Argument | Description | |----------|-----------------------------------| | name | Component name | | obj | Component definition |

Usage:

import {ComponentRegistry} from '@enonic/nextjs-adapter';

const definition = {}

ComponentRegistry.addMacro('macro-name', definition);

static getMacro(name: string): ComponentDefinition | undefined

static getPart(name: string): ComponentDefinition | undefined

static getLayout(name: string): ComponentDefinition | undefined

static getPage(name: string): ComponentDefinition | undefined

static getContentType(name: string): ComponentDefinition | undefined

static getComponent(name: string): ComponentDefinition | undefined

Gets the component definition stored in ComponentRegistry by its name.

NOTE: Read addComponent note before using getComponent.

| Argument | Description | |----------|----------------| | name | Component name |

Usage:

import {ComponentRegistry} from '@enonic/nextjs-adapter';

const definition = ComponentRegistry.getMacro('macro-name');

Response type: component definition

static getMacros(): ComponentDefinition[]

static getParts(): ComponentDefinition[]

static getLayouts(): ComponentDefinition[]

static getPages(): ComponentDefinition[]

static getContentTypes(): ComponentDefinition[]

static getComponents(): ComponentDefinition[]

Gets all component definitions stored in ComponentRegistry.

NOTE: Read addComponent note before using getComponents.

Usage:

import {ComponentRegistry} from '@enonic/nextjs-adapter';

const macros = ComponentRegistry.getMacros();

Response type: List of component definitions

UrlProcessor

Helper singleton for processing URLs.

static process(url: string, meta: MetaData, serverSide = false, isResource = false): string

Processes the absolute URL to become relative for the current viewer, while keeping in mind Next.js assets and Enonic XP binary content links

NOTE: There are convenience aliases to this function called getUrl() and getAsset()

| Argument | Description | |--------------|------------------------------------------------------------------------------| | url | Absolute URL | | meta | Runtime data returned by fetchContent | | serverSide | Whether URL is going to be used on the server side (Skips adding basePath) | | isResource | Whether URL is a resource (Skips adding locale) |

Usage:

import {UrlProcessor} from '@enonic/nextjs-adapter';

const url = UrlProcessor.process('http://www.some.site.com/url/to/content', meta, true, false);

static processSrcSet(srcset: string, meta: MetaData): string

Processes the image srcset attribute to transform each URL with process method

| Argument | Description | |----------|---------------------------------------------------------| | srcset | Value of the srcset attribute | | meta | Runtime data returned by fetchContent |

Usage:

import {UrlProcessor} from '@enonic/nextjs-adapter';

const url = UrlProcessor.processSrcSet('<srcset value>', meta);

static setSiteKey(key: string): void

Sets the site key value that is needed for correct absolute URL processing. It is automatically done by nextjs-adapter so you don't have to do it.

WARNING: Overriding this value may result in wrong URL processing !

| Argument | Description | |----------|-------------| | key | Site key |

Usage:

import {UrlProcessor} from '@enonic/nextjs-adapter';

UrlProcessor.setSiteKey('<site key>');

static isMediaLink(ref: string, linkData: LinkData[]): boolean

Checks if link data array contains link with provided ref. Positive result means that this is a link to Enonic XP content.

NOTE: link data array is contained in response of the query generated by richTextQuery('fieldName')

| Argument | Description | |------------|-----------------| | ref | Link ref | | linkData | Link data array |

Usage:

import {UrlProcessor} from '@enonic/nextjs-adapter';

UrlProcessor.isMediaLink('<link ref>', linkData);

static isContentImage(ref: string, imageData: ImageData[]): boolean

Checks if image data array contains image with provided ref. Positive response means that this is an Enonic XP image.

NOTE: image data array is contained in response of the query generated by richTextQuery('fieldName')

| Argument | Description | |-------------|------------------| | ref | Image ref | | imageData | Image data array |

Usage:

import {UrlProcessor} from '@enonic/nextjs-adapter';

UrlProcessor.isContentImage('<image ref>', imageData);

Enonic XP locale mapping functions

In order to create association between locale and Enonic XP project, ENONIC_MAPPINGS environment variable should be set. Functions for reading those mappings are available at @enonic/nextjs-adapter.

getLocaleMapping(context: Context): LocaleMapping

| Argument | Description | |-----------|------------------| | context | Context object |

Usage:

import {getLocaleMapping} from '@enonic/nextjs-adapter';

const mapping = getLocaleMapping({
    contentPath: '/current/content/path',
    locale: 'en',
});

Response type:

export interface LocaleMapping {
    default: boolean
    project: string
    site: string
    locale: string
}

getLocaleMappingByProjectId(projectId?: string, useDefault = true): LocaleMapping

| Argument | Description | |--------------|------------------------------------------------------------| | projectId | Enonic project ID (Optional) | | useDefault | Use default locale as fallback (Optional, default: true) |

Usage:

import {getLocaleMappingByProjectId} from '@enonic/nextjs-adapter';

const mapping = getLocaleMappingByProjectId('project-id', true);

Response type

getLocaleMappingByLocale(locale?: string, useDefault = true): LocaleMapping

| Argument | Description | |--------------|------------------------------------------------------------| | locale | locale ID (Optional) | | useDefault | Use default locale as fallback (Optional, default: true) |

Usage:

import {getLocaleMappingByLocale} from '@enonic/nextjs-adapter';

const mapping = getLocaleMappingByLocale('en', true);

Response type

getRequestLocaleInfo(context: Context): LocaleMapping

Attempts to get the locale info from the request. Along with that it also returns the default locale and all configured locales.

| Argument | Description | |-----------|------------------| | context | Context object |

Usage:

import {getRequestLocaleInfo} from '@enonic/nextjs-adapter';

const mapping = getRequestLocaleInfo({
    contentPath: '/current/content/path',
    locale: 'en',
});

Response type:

interface LocaleInfo {
    locale: string,
    locales: string[],
    defaultLocale: string
}

I18n localization functions

These functions can be used both on server and client sides, but it is recommended to use native React.js context classes for client-side localization.

static I18n.setLocale(locale: string): Promise<Dict>

Sets the current locale and returns the dictionary for it.

| Argument | Description | |----------|----------------------| | locale | Locale id, i.e. en |

Usage:

import {I18n} from '@enonic/nextjs-adapter';

const dict = await I18n.setLocale('en');

Response type:

interface Dict {
    [key: string]: string
}

static I18n.localize(key: string, ...args: any[]): string

Localizes the static text by key and replaces placeholders with provided arguments.

| Argument | Description | |-----------|--------------------| | key | text key | | ...args | template arguments |

Usage:

import {I18n} from '@enonic/nextjs-adapter';

const localizedText = I18n.localize('text.key', 'value1', 'value2');

static I18n.getLocale(): string

Returns the current locale.

Usage:

import {I18n} from '@enonic/nextjs-adapter';

const locale = I18n.getLocale();

static I18n.getDictionary(): Dict

Returns dictionary for the current locale.

Usage:

import {I18n} from '@enonic/nextjs-adapter';

const dict = I18n.getDictionary();

Response type

Utility functions

There is also a number of utility constants and functions available at @enonic/nextjs-adapter.

import {CATCH_ALL} from './constants';

IS_DEV_MODE;                // True if current mode == development

APP_NAME;                   // Name of the app defined in .env files

APP_NAME_UNDERSCORED;       // APP_NAME with underscores instead of dots

APP_NAME_DASHED;            // APP_NAME with dashes instead of dots

CATCH_ALL;                  // Catch all component name

PORTAL_COMPONENT_ATTRIBUTE; // Portal component attribute name

PORTAL_REGION_ATTRIBUTE;    // Portal region attribute name

JSESSIONID_HEADER;          // JSESSIONID header name

PROJECT_ID_HEADER;          // Project ID header name

RENDER_MODE_HEADER;         // Render mode header name

XP_BASE_URL_HEADER;         // XP base URL header name

enum XP_REQUEST_TYPE {      // Enum for XP request types
    COMPONENT = 'component',
    TYPE = 'type',
    PAGE = 'page',
}

enum RENDER_MODE {          // Enum for render modes
    INLINE = 'inline',
    EDIT = 'edit',
    PREVIEW = 'preview',
    LIVE = 'live',
    ADMIN = 'admin',
    NEXT = 'next',
}

enum XP_COMPONENT_TYPE {    // Enum for XP component types
    PART = 'part',
    LAYOUT = 'layout',
    TEXT = 'text',
    FRAGMENT = 'fragment',
    PAGE = 'page',
}

// Sanitizes text according to graphql naming spec http://spec.graphql.org/October2021/#sec-Names
const sanitizeGraphqlName = (text: string) => string;

// Returns common part of 2 strings
const commonChars = (s1?: string, s2?: string) => string;

// Returns full content api URL with current project and branch appended
const getContentApiUrl = (context: Context) => string;

Views

They are located in @enonic/nextjs-adapter/views folder. Each view is a default export in the corresponding file.

<MainView common="common" data="data" page="page" meta="meta">

The main view of the application. It accepts the result of fetchContent method. Should be default export from your next.js route

| Argument | Type | Description | |----------|-----------------------------------|-----------------------------| | common | Record<string, any> &#124; null | Result of the common query | | data | Record<string, any> &#124; null | Result of the graphql query | | page | PageComponent &#124; null | Page structure | | meta | MetaData | Runtime info |

Usage:

import MainView from '@enonic/nextjs-adapter/views/MainView';
import fetchContent from '@enonic/nextjs-adapter';

export async function getServerSideProps(context: Context) {
    const props = fetchContent('/content/path', context);
    return {
        props
    }
}

export default MainView;

<StaticContent condition="true" tag="div">

Tag for disabling client side hydration if the condition is true. This will remove interactivity from children.

| Argument | Type | Description | |--------------------|-----------|------------------------------------| | condition = true | Boolean | Condition to trigger static output | | tag = 'div' | String | Html tag to use for static output |

Usage:

import StaticContent from '@enonic/nextjs-adapter/views/StaticContent';

<StaticContent condition={true} tag="div"> ...child elements... </StaticContent>

<RichTextView className="css-class" tag="section" data={data} meta={meta}, renderMacroInEditMode="true", customReplacer={customReplacerFn}>

Tag for displaying contents of html area input types. Takes care of processing macros, URLs and images inside.

| Argument | Type | Description | |--------------------------------|----------------|---------------------------------------------------------------------------------------------------| | data | RichTextData | Rich text data | | meta | MetaData | Runtime data returned by fetchContent method. | | customReplacer | Replacer | Function to do custom element processing. Not invoked for image, link and macro nodes. Optional | | className | String | Class name to add to the root html element. Optional | | renderMacroInEditMode = true | boolean | Flag passed to macros telling if they should render themselves in edit mode | | tag = 'div' | String | Html tag to use as a root |

TIP! There is a utility function richTextQuery(fieldName) generating part of the graphql query to obtain RichTextData for html area input types.

Usage:

import RichTextView from '@enonic/nextjs-adapter/views/RichTextView';

<RichTextView data={richTextData} meta={meta} tag="section" className="rich-text-view" renderMacroInEditMode="false"></RichTextView>

<Regions page={page} meta={meta} name="main" common={common}>

Tag for rendering page regions. It is useful when implementing custom page views. All necessary data can be acquired by running fetchContent()

| Argument | Type | Description | |----------|-------------------------|-------------------------------------| | page | PageData &#124; null; | Page structure | | meta | MetaData | Runtime info | | name | String | Render only this region. Optional | | common | any | Result of common query. Optional |

Usage:

import StaticContent from '@enonic/nextjs-adapter/views/StaticContent';

<StaticContent condition={true} tag="div"> ...child elements... </StaticContent>

Known problems

In some cases, when using in NextJS application, a build errors related to the JSX may appear, that state the following:

You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders

To prevent these errors, it is necessary to transpile the library using something like next-transpile-modules. Install it as dev dependency and call it explicitly from you NextJS config:

next.config.js

const withTM = require('next-transpile-modules')(['@enonic/nextjs-adapter']);

module.exports = withTM({});