The packages export API

Entry point names

Right now we have pwa, amp and server. I don’t quite like pwa anymore. pwa means other things in 2019.

I will stick to client.js and server.js inside pwa and amp tho. I think it’s clear. But I think server/index.js is confusing when compared to pwa/server.js.

Both pwa and amp are dynamic coming from settings “matches”. There’s a default match so maybe it can be default and amp but I don’t quite like default either as it doesn’t mean anything. I think we also need a better name for “matches”.

So right now:

/my-theme-or-extension
  /src
    /pwa (default?)
      client.js
      server.js
    /amp
      client.js
      server.js
    /server
      index.js

@development-team: I’m sure we can do better! any ideas?

Export different namespaces

We want to incorporate multiple namespaces per package. That means we can’t use the export default for React anymore but we don’t need to code split the packages themselves so we are ok.

We need a name. My first thought is root but I don’t like it very much. Ideas welcomed.

Fills and Slots

We also need to make sure that <Fills /> are included after the <Slots /> or they won’t work.

The easiest way to solve it is to have a separate export for them, like fills.

@David do you remember which other approaches we discussed about this?

Libraries (effects)

Our old libraries (env) now go inside the store, in the Overmind effects.


Let’s work on our exports!

// pwa/client.js
const theme = {
  root: Theme, // This is the same than our old default export.
  fills: Fills, // This are the fills, separated from the root.
  Store: ThemeStore, // This is the Overmind store, just like our old MST store.
};

const router = {
  Store: SimpleRouterStore, // Expose the store of the simple-router.
};

// This theme has its own comments package.
const comments = {
  components: Components, // Components to be used by other extensions
  Store: CommentsStore,
}

// Export namespaces
export default {
  theme,
  router,
  comments,
}

By the way, I thought we needed an additional export for the HTML template but I think we can use the server/index.js itself: The Frontity Server Architecture

In order to be able to help:

I don’t understand what are pwa, amp and server :confused:

We had different namespaces before, right? why can’t we use default now?

No, we didn’t. Only one namespace was allowed per package.

The entry points of the old framework: pwa, amp and a special one for server configuration. For example: https://github.com/wp-pwa/disqus-comments/tree/dev/src

What about this:

/my-theme-or-extension
  /src
    /react
      csr.js
      ssr.js
      inline.js
      server.js
    /amp
      ssr.js
      server.js

Instead of pwa, react.

The first folder is the mode. Then four possible entry points:

  • csr: client-side rendering
  • ssr: server-side rendering
  • inline: bundle for inline javascript, i.e: our lazy-image with intersection observer polyfill.
  • server: server middleware (koa)

If we export the server middleware in the ssr file maybe we can go back to client.js and server.js:

// react/server.js
const theme = {
  root: ThemeRoot,
  store: ThemeStore,
  server: ServerMiddleware,
};

export default {
  theme
}
/my-theme-or-extension
  /src
    /react
      client.js
      server.js
      inline.js
    /amp
      server.js
1 Like

I find the react name confusing. Both are going to be built with react. Maybe it would make more sense to call them html and amp, or web and amp. (As a side note, I think that pwa and amp are good enough)

I like it :slight_smile:

You’re right, both use react so it’s not a good name. But it happens the same with html and web too! Why is this so difficult :laughing:

What about frontity and amp?

We are finally using html and amp as suggested by @orballo.

For the package export interface we are going to use this:

Which is basically something like this:

// html/client.js
export default {
  roots: {
    theme: ThemeRoot
  },
  fills: {
    theme: ThemeFills
  },
  state: {
    settings: {
       theme: themeSettings,
    },
    theme: themeState,
    comments: commentsState
  },
  actions: {
    theme: themeActions,
    comments: commentsActions,
  },
  libraries: {
    comments: {
       Comment
    }
   }
}
1 Like

I have a question about libraries! :thinking:

Libraries are meant to be exposed as shown above, but I don’t know how other packages could use them, particularly if someone wants to use a library inside an action (an action only receive state from args and so it cannot access to libraries).

Is it supposed that libraries will only be able to be used from the React components (using a hook or something)?

Sorry David, I forgot to add that information.

Packages can be either objects like this:

export default {
  roots...
  state...
  actions...
}

or functions that return the Package object like this:

export default ({ libraries }) => {
  return {
    roots...
    state...
    actions...
  }
}

In both scenarios, the exported type should be the type of the object, not the function:

interface MyPackage extends Package {
  roots...
  state...
  actions...
  libraries...
}

export default (
  { libraries }: 
  { libraries: MyPackage["libraries"] }
): MyPackage => {
  return {
    roots...
    state...
    actions...
  }
}

@David, do you need it? I wasn’t planning to have it ready for the beta.

I see, thanks @luisherranz!

Another question…

For those libraries that are classes and need to be instantiated, what would the best way to handle that case? I mean when you need an instance shared between packages.

Oh yes, that’s another thing I forgot to mention :sweat:

There’s a caveat with libraries: packages aren’t guaranteed to be run in any order and therefore the library you may be looking for could not be initializated.

For that reason, libraries cannot be used on creation. Actually, libraries will be an empty object until the creation has finished.

This is going to be the initialization flow:

Server

  1. Create empty object libraries.
  2. Merge all packages together. If function, pass libraries.
    • Packages can initializate their own libraries.
    • Packages shouldn’t use any external libraries yet.
  3. Populate libraries with the exported namespaces from all packages.
  4. Call the init action of each package, wait until they finish.
    • At this point, libraries is populated and can be used.
  5. Call the beforeSSR action of each package, wait until they finish.
  6. Do the server side rendering with the current state.
  7. Send both the state snapshot and the HTML to the client.
  8. Call the afterSSR action of each package (not sure about that, we didn’t need it in the past).

Client

  1. Receive the state and HTML.
  2. Create empty object libraries.
  3. Merge all packages together. If function, pass libraries.
  4. Apply the state snapshot received from the server.
  5. Populate libraries with the exported namespaces from all packages.
  6. Call the init action of each package, wait until they finish.
  7. Call the beforeCSR action of each package, wait until they finish.
  8. Do the client side rendering. It should match the HTML exactly.
  9. Call the afterCSR action of each package.

Currently, when I want to create an extension, a need two files for each mode. For html mode for example, I need to create html/client.ts and html/server.ts. That’s cool when I need two different exports, but it’s cumbersome when I want to export the same for both. Currently, we are doing something like:

// Files structure
html/
|_ client.ts
|_ server.ts
|_ index.ts

// client.ts
export { default } from ".";

// server.ts
export { default } from ".";

// index.ts
import theme from './components';

export default {
  roots: {
    theme
  }
};

@luisherranz do you think we can make @frontity/core to import from index.ts if client.ts or server.ts is missing? I don’t know if this is possible because of webpack.

Yes, it is possible because we are generating static files with those imports in the build process and we can check if those files exists.

The problem with that approach is that sometimes you only need a server.js file, for example for AMP. If Frontity doesn’t find a client.js, it doesn’t generate a client bundle. If people get used to use index.js by default, there’s no way to know which one needs a client bundle and which not.

Generating extra client bundles that are not going to be used (like in the case of AMP) doesn’t sound so bad by itself, they are not used anyway. The problem is that they will be included by the offline service worker wasting space and bandwidth on the final reader computer.

So I think it’s either a good idea because it simplifies the most common html case and a bad idea because it can cause problems if devs are used to see an index.js and are not aware that they should use only server.js in AMP.

Option 1

For the html mode you need a client.js and a server.js. If they are equal, you can create an index.js and use this in both your client.js and server.js files:

export { default } from ".";

For the amp mode, you only need a server.js.

Option 2

For the html mode you need an index.js file. If you want to have separate code for the client and server, create both a client.js and a server.js file.

For the amp mode, you only need a server.js. Do not use an index.js here.

2 Likes