ricokahler / next-plugin-preval

Pre-evaluate async functions during builds and import them like JSON
MIT License
255 stars 12 forks source link

Promise returned rather than resolved response #19

Closed riceboyler closed 3 years ago

riceboyler commented 3 years ago

Hey @ricokahler , I'm loving the idea behind this library, but I'm having a devil of a time getting it working. I'm trying to use it to solve the nearly universal problem of wanting to provide dynamic links in my navbar menu, but not wanting to repeat that code on every page.

My next.config.js looks like this:

// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx');
const withPlugins = require('next-compose-plugins');
const createNextPluginPreval = require('next-plugin-preval/config');
const withNextPluginPreval = createNextPluginPreval();

module.exports = withPlugins([withNx, withNextPluginPreval]);

That returns a promise inside of my component when I call the preval file.

blogPosts.preval.ts

import preval from 'next-plugin-preval';
import { Client } from '../prismic-configuration';
import Prismic from '@prismicio/client';

async function getRecentBlogPosts() {
  const blogPosts = await Client.query(Prismic.Predicates.at('document.type', 'blog_post'));

  const sortedPosts = blogPosts.results
    .sort((a, b) => {
      return a.last_publication_date > b.last_publication_date ? 1 : -1;
    })
    .slice(0, 3);

  return sortedPosts;
}

export default preval(getRecentBlogPosts());

And the component it's called in: header.tsx

import React, { useState, useEffect } from 'react';
import { INavbar, Navbar } from '@chtbks/steak-n-eggs';
import { Document } from '@prismicio/client/types/documents';

import { productBlocks, communityBlocks, helpBlocks } from './header/blocks';
import { PrismicResponses } from '../types/prismic_responses';
import useBlogPosts from '../hooks/blogPosts';
import sortedPosts from '../deps/blogPosts.preval';

interface BlogPostDocument extends Document {
  data: PrismicResponses.IBlogPost;
}

export const Header = () => {
  const blogPosts: BlogPostDocument[] = sortedPosts;

  const blogBlock = communityBlocks.find((item) => item.headerText === 'Blog');

  console.log(blogPosts);

  if (blogPosts && blogPosts.length) {
    const blogLinks: INavbar.INavLink[] = blogPosts.map((post) => {
      const link: INavbar.INavLink = {
        text: post.data.title[0].text,
        href: post.url
      };
      return link;
    });

    blogBlock.links = blogLinks;
  }

  return <Navbar productBlocks={productBlocks} communityBlocks={communityBlocks} helpBlocks={helpBlocks} />;
};

Header.dataHooks = [useBlogPosts];

export default Header;

The console.log above returns a Promise with the data inside of it. Unfortunately, TS isn't happy with using a .then on a non-function (and it looks weird, admittedly). I toyed with the configuration as somebody put in a different issue and attempted to execute the withNextPluginPreval in the next.config.js:

// eslint-disable-next-line @typescript-eslint/no-var-requires
const withNx = require('@nrwl/next/plugins/with-nx');
const withPlugins = require('next-compose-plugins');
const createNextPluginPreval = require('next-plugin-preval/config');
const withNextPluginPreval = createNextPluginPreval();

module.exports = withPlugins([withNx, withNextPluginPreval()]);

but that throws errors related to requiring an ESM module, so it's a no go (though interestingly, the code still executes and the data returned is not wrapped in a promise.

Any ideas how I could resolve this? This library seems like the most promising way to get the data I need in to getStaticProps without having to run the same codeblock on each page.

riceboyler commented 3 years ago

FYI, the error:

Error: Failed to pre-evaluate "/home/riceboyler/source/@chtbks/nx-web/apps/make-it-rain/deps/blogPosts.preval.ts". Error [ERR_REQUIRE_ESM]: Must use import to load ES Module: /home/riceboyler/source/@chtbks/nx-web/node_modules/next/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js
require() of ES modules is not supported.
require() of /home/riceboyler/source/@chtbks/nx-web/node_modules/next/node_modules/@babel/runtime/helpers/esm/asyncToGenerator.js from /home/riceboyler/source/@chtbks/nx-web/apps/make-it-rain/deps/blogPosts.preval.ts.preval-run.js is an ES module file as it is a .js file whose nearest parent package.json contains "type": "module" which defines all .js files in that package scope as ES modules.
Instead rename asyncToGenerator.js to end in .cjs, change the requiring code to use import(), or remove "type": "module" from /home/riceboyler/source/@chtbks/nx-web/node_modules/next/node_modules/@babel/runtime/helpers/esm/package.json.
riceboyler commented 3 years ago

FWIW, it looks like the error is related to the val-loader plugin itself. When I try to just run val-loader, I'm seeing the same issue. So, I'll close this, as I need to dig further into what's going on with that.

ricokahler commented 3 years ago

Sorry just seeing this. Let me know where you land. I tried to make this plugin compose well with others but that may not be the case! Trying to improve

Vadorequest commented 3 years ago

@ricokahler Did you had a chance taking a look into this? I'm running into a similar issue with async/await. Do you have any working example using async/await?

Vadorequest commented 3 years ago

@riceboyler FYI I've noticed this strange behavior:

Works

import { getAirtableSchema } from '@/modules/core/airtable/airtableSchema';
import consolidateSanitizedAirtableDataset from '@/modules/core/airtable/consolidateSanitizedAirtableDataset';
import fetchAndPrepareAirtableDatasets  from '@/modules/core/airtable/fetchAndSanitizeAirtableDatasets';
import { AirtableSchema } from '@/modules/core/airtable/types/AirtableSchema';
import { AirtableDatasets } from '@/modules/core/data/types/AirtableDatasets';
import { SanitizedAirtableDataset } from '@/modules/core/data/types/SanitizedAirtableDataset';
import preval from 'next-plugin-preval';

const fetchAirtableDatasetPreval = async (): Promise<SanitizedAirtableDataset> => {
  const airtableSchema: AirtableSchema = getAirtableSchema();
  const datasets: AirtableDatasets = await fetchAndPrepareAirtableDatasets(airtableSchema);

  return consolidateSanitizedAirtableDataset(airtableSchema, datasets.sanitized);
};

export const dataset = preval(fetchAirtableDatasetPreval());

export default dataset;

Fails

import { getAirtableSchema } from '@/modules/core/airtable/airtableSchema';
import consolidateSanitizedAirtableDataset from '@/modules/core/airtable/consolidateSanitizedAirtableDataset';
import { fetchAndPrepareAirtableDatasets }  from '@/modules/core/airtable/fetchAndSanitizeAirtableDatasets';
import { AirtableSchema } from '@/modules/core/airtable/types/AirtableSchema';
import { AirtableDatasets } from '@/modules/core/data/types/AirtableDatasets';
import { SanitizedAirtableDataset } from '@/modules/core/data/types/SanitizedAirtableDataset';
import preval from 'next-plugin-preval';

const fetchAirtableDatasetPreval = async (): Promise<SanitizedAirtableDataset> => {
  const airtableSchema: AirtableSchema = getAirtableSchema();
  const datasets: AirtableDatasets = await fetchAndPrepareAirtableDatasets(airtableSchema);

  return consolidateSanitizedAirtableDataset(airtableSchema, datasets.sanitized);
};

export const dataset = preval(fetchAirtableDatasetPreval());

export default dataset;

Depending how import { fetchAndPrepareAirtableDatasets } from '@/modules/core/airtable/fetchAndSanitizeAirtableDatasets'; is written, next-plugin-preval will fail invoking the function. (using default export works, using named export fails)

error - ./src/modules/core/airtable/fetchAirtableDataset.preval.ts
Error: Failed to pre-evaluate "/Users/vadorequest/dev/NRN/v2-mst-aptd-at-lcz-sty/src/modules/core/airtable/fetchAirtableDataset.preval.ts". TypeError: (0 , _fetchAndSanitizeAirtableDatasets.fetchAndPrepareAirtableDatasets) is not a function

<w> [webpack.cache.PackFileCacheStrategy] Skipped not serializable cache item 'Compilation/modules|/Users/vadorequest/dev/NRN/v2-mst-aptd-at-lcz-sty/node_modules/next/dist/build/webpack/loaders/next-babel-loader.js??ruleSet[1].rules[1].use!/Users/vadorequest/dev/NRN/v2-mst-aptd-at-lcz-sty/node_modules/next-plugin-preval/loader.js!/Users/vadorequest/dev/NRN/v2-mst-aptd-at-lcz-sty/src/modules/core/airtable/fetchAirtableDataset.preval.ts': No serializer registered for PrevalError

<w> while serializing webpack/lib/cache/PackFileCacheStrategy.PackContentItems -> webpack/lib/NormalModule -> webpack/lib/ModuleBuildError -> PrevalError

This happens when using import { fetchAndPrepareAirtableDatasets } from '@/modules/core/airtable/fetchAndSanitizeAirtableDatasets';, the {} make it crash.

Rule of thumb, always use the default exported function with preval.

This is probably a bug and should be fixed.