zorigitano / multipage-webpack-plugin

A plugin that makes handling templates and asset distribution for multi-page applications using webpack trivial
Apache License 2.0
170 stars 15 forks source link

Design and Unit Test Cases #4

Open TheLarkInn opened 7 years ago

TheLarkInn commented 7 years ago

MVP Features

TheLarkInn commented 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.

TheLarkInn commented 7 years ago

Tagged @nekr @addyosmani for ideas on service worker integration, are there any reasonable defaults that could be derrived or added.

TheLarkInn commented 7 years ago

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

TheLarkInn commented 7 years ago

Alright I'm going to tackle the first one since it's pretty easy for defaults.

Scenario (Defaults)

Fixture

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;

Templates

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
TheLarkInn commented 7 years ago

cc @collinstork

NekR commented 7 years ago

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

developit commented 7 years ago

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

NekR commented 7 years ago

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

developit commented 7 years ago

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.

TheLarkInn commented 7 years ago

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.

TheLarkInn commented 7 years ago

@NekR if you were interested I could create a separate issue which you could discuss potential design options for multi page apps.

NekR commented 7 years ago

@TheLarkInn yeah, that would great :-)

TheLarkInn commented 7 years ago

Created #7 for separate discussion thread.

addyosmani commented 7 years ago

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.

NekR commented 7 years ago

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

developit commented 7 years ago

Thought: provide a configuration the developer can use to instantiate OfflinePlugin, but keep them as peers?

TheLarkInn commented 7 years ago

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

TheLarkInn commented 7 years ago

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.

NekR commented 7 years ago

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?

TheLarkInn commented 7 years ago

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.

TheLarkInn commented 7 years ago

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.