Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Create a template which itself loads file processed by webpack #39

Closed
donaldpipowitch opened this issue Oct 8, 2020 · 15 comments
Closed

Comments

@donaldpipowitch
Copy link

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:

@bebraw
Copy link
Collaborator

bebraw commented Oct 8, 2020

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.

@bebraw
Copy link
Collaborator

bebraw commented Oct 8, 2020

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.

@donaldpipowitch
Copy link
Author

Do you want to give it a go?

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

@donaldpipowitch
Copy link
Author

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 ``;
    },
  }),
];

@bebraw
Copy link
Collaborator

bebraw commented Oct 8, 2020 via email

@donaldpipowitch
Copy link
Author

donaldpipowitch commented Oct 9, 2020

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

@bebraw
Copy link
Collaborator

bebraw commented Oct 9, 2020

@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' ]
}

@donaldpipowitch
Copy link
Author

donaldpipowitch commented Oct 12, 2020

@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?

@bebraw
Copy link
Collaborator

bebraw commented Oct 12, 2020

@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.

@donaldpipowitch
Copy link
Author

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
Copy link
Author

Okay... I don't think I know how to access the compiled initial chunk. 😅 https://github.com/donaldpipowitch/react-refresh-webpack-plugin-RefreshSig-bug/pull/4/files#diff-6d5e756cc942fd5f521d854dbb7bfe3bR54

@bebraw
Copy link
Collaborator

bebraw commented Oct 12, 2020

@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.

@donaldpipowitch
Copy link
Author

donaldpipowitch commented Oct 13, 2020

@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

@bebraw
Copy link
Collaborator

bebraw commented Oct 13, 2020

@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.

@bebraw bebraw closed this as completed Oct 13, 2020
@donaldpipowitch
Copy link
Author

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants