styleguidist/mini-html-webpack-plugin

Create a template which itself loads file processed by webpack

donaldpipowitch opened this issue · 15 comments

Continuing the discussion from this Twitter thread. Thank you so much for looking into it!

Problem description:
I'd like to generate an index.html which is a React template rendered at build time in a Node process. This template requires files which need to be processed by webpack loaders. I currently use html-webpack-plugin with this, but very often run into issues, because my setup seems to be too exotic. One of those issues was around React Fast Refresh and I created a repo for that (demo, workaround for the problem shown in the demo).

Interesting code snippets from the demo:

Yeah, I see. Basically we're looking to execute renderToString against a React component compiled by webpack.

Note that we already know the asset paths in template so I believe we might be able to leverage that. The basic solution goes something like this:

  1. Set up a separate entry for your initial JavaScript that should go through renderToString
  2. Use the chunks field to select all other JS chunks from your entries except initial (unless you need it in the output somehow)
  3. Define a template function and pick initial chunk from jsAttributes
  4. Use require against the path (you may need __dirname here to get a full path depending on the setup)
  5. Use renderToString against the module you imported
  6. Inject the rendered result to the returned HTML where you need it

Do you want to give it a go? I can likely do a small example over the next days as well.

I think https://www.npmjs.com/package/mini-html-webpack-plugin#custom-templates would give a great starting point for this type of work and I recommend reading the plugin source to understand better what it's exactly doing.

There's one more consideration. When using template like this during development, you should apply decache before require to get a non-cached version of the module. That should fix the HTML output.

I've done something like this before in my work and it's a cool way to handle SSR through the plugin while getting a nice development experience.

Do you want to give it a go?

Yeah, I'll try to find time later today or tomorrow. Thanks a lot!

3. Define a template function and pick initial chunk from jsAttributes

Could you clarify this a little bit more? I have written something like this and jsAttributes is undefined:

const entry = {
  app: ['./src/index.tsx'],
  initial: ['./src/components/loading-page.tsx'],
};

const plugins = [
  new MiniHtmlWebpackPlugin({
    chunks: ['app'],
    template({ jsAttributes }) {
      // jsAttributes === undefined
      return ``;
    },
  }),
];

Sure ❤️ Here is the example. Thank you very much for doing that in your free time. donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug#4

@donaldpipowitch I see now. I remembered the API wrong - the parameter you should be looking at is js and it contains the files (or a file in your case). That contains the emitted JS files. jsAttributes is passing through JS attributes from context.

Here's example output:

{
  publicPath: '',
  js: [ 'app.f0e295485fe05274bc06.js' ],
  map: [ 'app.f0e295485fe05274bc06.js.map' ]
}

@bebraw I tried a couple of things, but I'm stuck here currently: https://github.com/donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug/pull/4/files#diff-6d5e756cc942fd5f521d854dbb7bfe3bR48

Can I access the compiled initial chunk at this point?

@donaldpipowitch Oh, I see now - it's a chicken and egg problem possibly.

There's one option that's a little out of the box. Since the template function can be asynchronous, we could trigger webpack there through its function interface and capture the output from there. That would allow you to handle SSR within the template function and eventually return after it has completed. I've written a related example.

Phew. It's been a while since I used the WebPack programmatic API. I wonder if this will bite me at some point in the future. But let me try that just for the sake of this example. 🤔

@donaldpipowitch I mean literally using webpack to bundle the initial bundle and capturing it from the output as in my reference. The idea is to run webpack inside webpack to generate the bundle you need and once the secondary build is complete, run the result for SSR.

@bebraw Thank you for input. Look like I got it working. Crazy. I'm not entirely sold if this will come with other major downsides (first thing I needed to change was removing CleanWebpackPlugin). But indeed it seems to work. Definetely offers the best flexibility (e.g. it allows me to fix this problem easily pmmmwh/react-refresh-webpack-plugin#176).

donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug#4

@donaldpipowitch Cool, great to hear! It's not the most intuitive solution but it's a solution.

To improve the setup, you could add webpack-add-dependency-plugin against the module compiled within template (initial.js or so). Using this allows webpack to watch changes to it and trigger rebuild logic correctly without having to start the server. The plugin was designed indirect usage like this in mind.

That's an awesome suggestion. Thank you so much for all your support.