Open TheLarkInn opened 7 years ago
Wanted to start a thought-dump here on what we think should be a part of the initial design. The first requirements will probably support what we are needing in-house, but I would still like to have a set of defaults pulled in.
cc// @kenwheeler @developit because you showed some mild interest in this plugin, etc. Would love input if you have it.
Tagged @nekr @addyosmani for ideas on service worker integration, are there any reasonable defaults that could be derrived or added.
@mipearson @nateberkopec would love to have you and the rails community involved as this could really mutually benefit as you all consider replacing Sprockets with webpack by default.
Alright I'm going to tackle the first one since it's pretty easy for defaults.
webpack.config.js
const MultipageWebpackPlugin = require(multipage-webpack-plugin);
const config = {
entry: {
a: './src/a.js',
b: './src/b.js'
},
output: {
filename: '[name].chunk.js',
path: path.join(__dirname, "dist")
},
module: {
/* ... */
},
plugins: [
new MultipageWebpackPlugin()
]
};
module.exports = config;
What should we define as the default template output type?
We use the most simple and compatible template across MVC frameworks and that is .html
.
Since under the hood this POC leverages html-webpack-plugin
the output/emitted template file makes sense to be .html. By default there would be automatic handling of where script tags should be placed etc.
What should the default output path and organization (directory structure) for each template?
Each template should be emitted into their own subdirectory inside of the specified path found in config.output.path
. This sub directory by default would be sane to match the entry
name. This way each template can be accessed and served with simple http-server
or liteserver
or whatever else is out there.
Example derived from fixture above:
├── dist/
│ ├── a/
│ ├─── index.html
│ ├── b/
│ ├─── index.html
cc @collinstork
Well, offline-plugin
for sure 😁
If serious, it depends by a multi-page website's design. Reasonable default might be app-shell + streaming it with content requested from the server (server responds only with content, without app-shell).
@NekR That lines up nicely with my use-case for sure. I offline the app shell, which is then basically an offline Markdown reader, and then request & cache .md
files from a content/
directory as-needed.
@developit is it SPA? For SPA it's easier and offline-plugin
indeed supports that use case. For multi-page.. A but trucker and there are possibly perf downsides on big projects (which is why FB requested some additional features in SW spec to fix that). Anyway, it would be great to add multi-page app-shell support to the offline-plugin
.
It's an SPA currently yeah. I'm really just looking to pre-render a bunch of different entry pages. It's true though, I'd be giving up a bit of caching for the shell's HTML.
Yes this is why I listed as optional feature. I'm not 100% sure yet how the best practice for implementing service worker support for multi page applications.
@NekR if you were interested I could create a separate issue which you could discuss potential design options for multi page apps.
@TheLarkInn yeah, that would great :-)
Created #7 for separate discussion thread.
Thanks for the cc!
Reading through the problem statement in the readme, one thing that would be valuable is understanding the scope of the solution for multi-page apps. Is the goal here to create a turnkey all-in-one solution so folks don't have to thing about their Webpack plugin config per entry/route for anything else? I ask because scope might help understand whether it makes sense or not to take in H/2 splitting, offline and some of the other optional pieces mentioned.
What should we define as the default template output type?
HTML sgtm. I assume that under the hood, either the HTML Webpack plugin or otherwise would still let you configure this per entry in case you're working with a mix of client and server-side templates.
Should there be any consideration for css chunks? Or should this happen automagically?
I wouldn't say this should happen automagically, but is the goal to split these out so you're only loading in the styles for an entry/template/route rather than the whole kitchen sink?
Should there be a default vendor chunk? Should it be determined by minChunks: module => module.resource.test(/node_modules/)
I think that'd be pretty nice.
Should these be called '[name].chunk.js'?
👍
Order of scripts inside template should just work every time regardless of the case
Yeah. Agree. Although, I assume you're abstracting away the end-user needing to think about script order at all to an extent.
Inline bootstrap chunk?
👍
Hashing by default also sounds good. TBH, I'd love it if the output of this project was code-splitting per route/entry, with common vendor chunks handled for you automatically and any long-term caching settings pre-setup and bootstrapping issues handled for you too. These are the pieces I most often find myself having to manually add and wrangle in Webpack.
Aggressive splitting is (for now) something I would actually have folks opt-into by manually adding it in or opting in via a boolean config. Just to make sure they're enabling it when they know their server setup is going to be able to take advantage of it (H/2 Push or otherwise).
Offline support
After having tried to enable "offline by default" workflows in a few different CLI/starter kit projects over the last 2 years, I've come to the opinion that this should be an opt-in (either boolean or install yourself). Too often I've found developers unaware of Service Worker trying to understand why their new builds are still using "old" code, but they don't realize it's the offline caching kicking in.
I do think it's worth encouraging caching, but like HTTP/2 and Server Push I'd make it something you explicitly say you want.
@addyosmani
After having tried to enable "offline by default" workflows in a few different CLI/starter kit projects over the last 2 years, I've come to the opinion that this should be an opt-in (either boolean or install yourself).
Oh, I didn't realize that Sean wants it by default in this plugin. @TheLarkInn plugin inside plugin? Hmm.
Too often I've found developers unaware of Service Worker trying to understand why their new builds are still using "old" code, but they don't realize it's the offline caching kicking in.
Yeah, this happens all the time with react-boilerplate
and offline-plugin
. Maybe it can show "Update is ready" toast by default.
Thought: provide a configuration the developer can use to instantiate OfflinePlugin, but keep them as peers?
@addyosmani thank you for the thoughtful responses.
Is the goal here to create a turnkey all-in-one solution so folks don't have to thing about their Webpack plugin config per entry/route for anything else?
Yes essentially. So for most MVC server routed web applications, you will have an entry point for essentially every "rendered page instance". The challenge in this is that when you get to managing hashed chunks and files, that you would essentially have to updated the changed hash files for every single page affected.
For single page apps, html-webpack-plugin
makes this super simple because it manages the injection of scripts into a "templated" .html
file.
To take this approach it essentially requires the users to configure like seen here
const templatesFn = (modules, twigRoot, assetsRoot, shared) => {
return Object.keys(modules).map((entryName) => { // for each entry point
return new HtmlWebpackPlugin({ // create html-webpack-plugin instance
template: `${assetsRoot}/webpack.template.hbs`, //use a base html template tailored to MVC lang (in our case we use twig for Laravel)
filename: `${twigRoot}/webpack-bundles/${entryName}.twig`, // We need to specify the output path for each of these twig templates (they could lie in a non-patterned location
chunks: ['inline', 'vendors', entryName, `${shared}`] // Only include the following: entry chunk for that page, shared chunk if applicable, inline for the webpack bootstrap, and a vendors chunk
})
});
}
Most of this information for out of the box standard index.html templates would require little to know extra configuration besides the plugin reading the config and generating the correct html-webpack-plugin
instances.
In the same way, CommonsChunkPlugin is also applied behind the scenes so that this can be easily implemented by the user.
Oh, I didn't realize that Sean wants it by default in this plugin. @TheLarkInn plugin inside plugin? Hmm.
@NekR Yeah, a lot of struggles with using html-webpack-plugin is that you have to configure a new instance of that plugin for every "page" in a multipage app.
provide a configuration the developer can use to instantiate OfflinePlugin, but keep them as peers? This was my initial thoughts. It could be as simple as
MultipageWebpackPlugin({offline: true})
or it could be more involved but would still be treated as peers.
Let me know if this answers most of the questions.
Hashing by default also sounds good. TBH, I'd love it if the output of this project was code-splitting per route/entry, with common vendor chunks handled for you automatically and any long-term caching settings pre-setup and bootstrapping issues handled for you too. These are the pieces I most often find myself having to manually add and wrangle in Webpack.
Yes agreed. Currently I don't have the hashing portions of this setup, but I think I'll have a separate story and design for that to determine how the plugin should detect a production environment build (besides node-env etc. However, the CommonsChunkPlugins (for the exception of the dynamic vendor chunking) has been implemented so far. (I stopped there because I wanted to do a more strict TDD and BDD for this plugin.
Why would you generate HTML files for every single page/route? Can't you just generate a template which will be populated by the server with contents?
That is possible, it wouldn't be for EVERY route IE (generated template files). There are exceptions however the idea is that for every unique template or page your want 1 single entry and an emitted template.
That is possible, it wouldn't be for EVERY route IE (generated template files). There are exceptions however the idea is that for every unique template or page your want 1 single entry and an emitted template.
MVP Features
minChunks: module => module.resource.test(/node_modules/)
'[name].chunk.js'
?Optional Features To Investigate
stats.compilation.entrypoints
commons vendor
could be created for each "entry" as well. This is farfetched however valid.