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:
- Here the template requires a file which is written in
.tsx
. webpack.config.js
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:
- Set up a separate entry for your
initial
JavaScript that should go throughrenderToString
- Use the
chunks
field to select all other JS chunks from your entries exceptinitial
(unless you need it in the output somehow) - Define a
template
function and pickinitial
chunk fromjsAttributes
- Use
require
against the path (you may need__dirname
here to get a full path depending on the setup) - Use
renderToString
against the module you imported - 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 pickinitial
chunk fromjsAttributes
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. 🤔
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
@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.