Code Monkey home page Code Monkey logo

webpack-flush-chunks's Introduction

Edit Redux-First Router Demo Edit Redux-First Router Demo

Webpack Flush Chunks

Version Build Status Coverage Status GPA Downloads License

🍾🍾🍾 GIT CLONE LOCAL DEMO πŸš€πŸš€πŸš€
Webpack 4 demo update in progress

Now supports Webpack 4 aggressive code splitting We have updated webpack-flush-chunks to now support more complex code splitting! webpack-flush-chunks enables developers to leverage smarter and less wasteful chunking methods avaliable to developers inside of Webpack.

Use this package server-side to flush webpack chunks from React Universal Component or any package that flushes an array of rendered moduleIds or chunkNames. The preferred approach is chunkNames, as that's what babel-plugin-universal-import focuses on.

import { flushChunkNames } from 'react-universal-component/server'
import flushChunks from 'webpack-flush-chunks'

const app = ReactDOMServer.renderToString(<App />)
const { js, styles } = flushChunks(webpackStats, {
  chunkNames: flushChunkNames()
})

res.send(`
  <!doctype html>
  <html>
    <head>
      ${styles}
    </head>
    <body>
      <div id="root">${app}</div>
      ${js}
    </body>
  </html>
`)

Webpack 4 Aggressive Code Splitting Support

This plugin allows for complex code splitting to be leveraged for improved caching and less code duplication! Below are two examples of well tested splitting configurations. If you experience any issues with bespoke optimization configurations, we would love to hear about it!

Before:

Before this update, developers were limited to a single chunk strategy. What the strategy did was give developers a similar chunk method to CommonsChunkPlugin that was used in Webpack 3. We did not support AggressiveSplittingPlugin

    optimization: {
        runtimeChunk: {
            name: 'bootstrap'
        },
        splitChunks: {
            chunks: 'initial',
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    name: 'vendor'
                }
            }
        }
    },

After:

Now you can use many flexible code splitting methods, like the one below. Webpack encourages aggressive code splitting methods, so we jumped on the bandwagon and did the upgrades. Just like before, we use the chunkNames generated - then we can look within the Webpack 4 chunk graph and resolve any other dependencies or automatically generated chunks that consist as part of the initial chunk.

We can load the nested chunks in the correct order as required and if many chunks share a common chunk, we ensure they load in the correct order, so that vendor chunks are always available to all chunks depending on them without creating any duplicate requests or chunk calls.

    optimization: {
        splitChunks: {
            chunks: 'async',
            minSize: 30000,
            minChunks: 1,
            maxAsyncRequests: 5,
            maxInitialRequests: 3,
            automaticNameDelimiter: '~',
            name: true,
            cacheGroups: {
                vendors: {
                    test: /[\\/]node_modules[\\/]/,
                    priority: -10
                },
                default: {
                    minChunks: 2,
                    priority: -20,
                    reuseExistingChunk: true
                }
            }
        }
    }

The code has been cracked for while now for Server Side Rendering and Code-Splitting individually. Accomplishing both simultaneously has been an impossibility without jumping through major hoops or using a framework, specifically Next.js. Our tools are for "power users" that prefer the frameworkless approach.

Webpack Flush Chunks is essentially the backend to universal rendering components like React Universal Component. It works with any "universal" component/module that buffers a list of moduleIds or chunkNames evaluated.

Via a simple API it gives you the chunks (javascript, stylesheets, etc) corresponding to the modules that were synchronously rendered on the server, which without this package would be asynchronously rendered on the client. In doing so, it also allows your first client-side render on page-load to render async components synchronously!

This solves the problem of having to make additional requests to get async components plus React checksum mismatches when the client expects to render a <Loading /> component.

It offers 2 functions flushChunks and flushFiles, which you call immediately after ReactDOMServer.renderToString. They are used in server-rendering to extract the minimal amount of chunks to send to the client, thereby solving a missing piece for code-splitting: server-side rendering.

The dream of code-splitting everywhere is finally here.

Reactlandia Articles:

Installation

yarn add react-universal-component webpack-flush-chunks 
yarn add --dev babel-plugin-universal-import extract-css-chunks-webpack-plugin
  • Babel Plugin Universal Import is used to make react-universal-component as frictionless as possible. It removes the need to provide additional options to insure synchronous rendering happens on the server and on the client on initial load. These packages aren't required, but usage as frictionless as possible.

  • Extract Css Chunks Webpack Plugin is another companion package made to complete the CSS side of the code-splitting dream. Its also a standalone plugin thats great for codesplitting css, with built-in HMR

If you like to move fast, git clone the universal-demo. We are working on a Webpack 4 demo

How It Works

React Universal Component, when used on the server, skips the loading phase and syncronously renders your contained component, while recording the ID of its corresponding module. React Universal Component may be used multiple times and therefore may record multiple split points. flushChunks/flushFiles is then able to determine the minimal set of chunks required to re-render those modules/components on the client. From there it outputs strings, arrays or React components containing the precise javascript files (and CSS files) to embed in your HTML response.

The result is a server-rendered response whose "checksum" matches the one generated on the client, so that another client render is not needed, and more importantly so that another request to the server for an additional chunk is not needed.

For future imports performed on user navigation, the "dual-import" mechanism of babel-plugin-universal-import will request a stylesheet. To accomplish that, a hash of chunk names to stylsheets is provided so you can embed it in the page, similar to what webpack does with your js chunks in its bootstrap code.

Before we examine how to use flushChunks/flushFiles, let's take a look at the desired output. It's something like this:

<head>
  <link rel='stylesheet' href='/static/0.css' />
  <link rel='stylesheet' href='/static/7.css' />
  <link rel='stylesheet' href='/static/main.css' />
</head> 

<body>
  <div id="react-root"></div>

  <!-- before entry chunks -->
  <script type='text/javascript' src='/static/bootstrap.js'></script>
  <script type='text/javascript' src='/static/vendor.js'></script>

  <!-- dynamic chunks -->
  <script type='text/javascript' src='/static/0.js'></script>
  <script type='text/javascript' src='/static/7.js'></script>

  <!-- after entry chunks -->
  <script type='text/javascript' src='/static/main.js'></script>
</body>

Notice common vendor and bootstrap chunks at the beginning and your main entry bundle (main) at the end. Notice that chunks 0 and 7 are served, but not chunks 1-6 or 8+. That's a lot of bytes saved in initial requests!

Because of the way Webpack works where "bootstrap" code must be run before any additional chunks can be registered, it's imperative bootstrap and common chunks are generated and placed at the beginning, thereby allowing you to place dynamic chunks before your entry chunk which kickstarts app rendering.

In conjunction with your Webpack configuration (which we'll specify below), Webpack Flush Chunks solves these problems for you by consuming your Webpack compilation stats and generating strings, arrays and components you can embed in the final output rendered on the server.

.babelrc:

{
  "plugins": ["universal-import"]
}

src/components/App.js:

import universal from 'react-universal-component'

const UniversalComponent = universal(props => import(`./${props.page})

export default () =>
  <div>
    <UniversalComponent page='Foo' />
  </div>

server/render.js:

import ReactDOMServer from 'react-dom/server'
import { flushChunkNames } from 'react-universal-component/server'
import flushChunks from 'webpack-flush-chunks'

const app = ReactDOMServer.renderToString(<App />)
const chunkNames = flushChunkNames()
const { js, styles } = flushChunks(stats, { chunkNames })

res.send(`
  <!doctype html>
  <html>
    <head>
      ${styles}
    </head>
    <body>
      <div id="root">${app}</div>
      ${js}
    </body>
  </html>
`)

et voila!

Note: if you require a less automated approach where you're given just the stylesheets and scripts corresponding to dynamic chunks (e.g. not main.js), see flushFiles in the the low-level API section.

Options API:

flushChunks(stats, {
  chunkNames: ReactUniversalComponent.flushChunkNames(),
  before: ['bootstrap', 'vendor'],                // default
  after: ['main'],                                // default
  outputPath: path.resolve(__dirname, '../dist'), // required only if you want to serve raw CSS
})
  • chunkNames - ***array of chunks flushed from react-universal-component

  • before - array of named entries that come BEFORE your dynamic chunks: A typical pattern is to create a vendor chunk. A better strategy is to create a vendor and a bootstrap chunk. The "bootstrap" chunk is a name provided to the CommonsChunkPlugin which has no entry point specified for it. The plugin by default removes webpack bootstrap code from the named vendor common chunk and puts it in the bootstrap chunk. This is a common pattern because the webpack bootstrap code has info about the chunks/modules used in your bundle and is likely to change, which means to cache your vendor chunk you need to extract the bootstrap code into its own small chunk file. If this is new to you, don't worry. Below you will find examples for exactly how to specify your Webpack config. Lastly, you do not need to provide this option if you have a bootstrap chunk, or vendor chunk or both, as those are the defaults.

Mostly related to Webpack 2 & 3. It is still very useful if you need to load a specific chunk name first webpack-flush-chunks now can rely on a better chunk graph provided by Webpack 4 - chunks are loaded in the correct order with more autonomy.

  • after - array of named entries that come AFTER your dynamic chunks: Similar to before, after contains an array of chunks you want to come after the dynamic chunks that your universal component flushes. Typically you have just a main chunk, and if that's the case, you can ignore this option, as that's the default.

  • outputPath - absolute path to the directory containing your client build: This is only needed if serving css embedded in your served response HTML, rather than links to external stylesheets. I.e. if you are using the Css and css values in the return API described in the next section. It's needed to determine where in the file system to find the CSS that needs to be extract into an in-memory string. Keep in mind if you're rendering the server with Webpack, filesystem paths may not match up, so it's important to accurately pass the outputPath to your serverRender method. We recommend to do this by running your server express/koa/hapi/etc code via Babel and then by requiring your Webpack server bundle into it. See one of our boilerplates for an example.

Return API:

The return of flushChunks provides many options to render server side requests, giving you maximum flexibility:

const {
  // react components:
  Js,     // javascript chunks
  Styles, // external stylesheets
  Css,    // raw css

  // strings:
  js,     // javascript chunks
  styles, // external stylesheets
  css,    // raw css

  // arrays of file names:
  scripts,
  stylesheets,

  // cssHash for use with babel-plugin-dual-import
  cssHashRaw, // hash object of chunk names to css file paths
  cssHash,    // string: <script>window.__CSS_CHUNKS__ = ${JSON.stringify(cssHashRaw)}</script>
  CssHash,    // react component of above

  // important paths:
  publicPath,
  outputPath
} = flushChunks(moduleIds, stats, options)

Let's take a look at some examples:

Webpack Configuration

client:

const ExtractCssChunks = require('extract-css-chunks-webpack-plugin')

entry: [
  path.resolve(__dirname, '../src/index.js'),
],
module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: 'babel-loader',
    },
    {
      test: /\.css$/,
      use: [
       ExtractCssChunks.loader,
       {
        loader: 'css-loader',
        options: {
          modules: true,
          localIdentName: '[name]__[local]--[hash:base64:5]'
        }
      ]
    }
  ]
},
plugins: [
  new ExtractCssChunks(),                     
  ...

server:

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: 'babel-loader',
    },
    {
      test: /\.css$/,
      exclude: /node_modules/,
      use: {
        loader: 'css-loader/locals',          // notice you're using the `locals` file as your loader
        options: {
          modules: true,
          localIdentName: '[name]__[local]--[hash:base64:5]'
        }
      }
    }
  ]
}
plugins: [
  new webpack.optimize.LimitChunkCountPlugin({
    maxChunks: 1,                               
  })
  ...
  • The LimitChunkCountPlugin with maxChunks: 1 insures only one file is generated for your server bundle so it can be run synchronously.

Externals

If you're specifying externals to leave unbundled, you need to tell Webpack to still bundle react-universal-component and webpack-flush-chunks so that they know they are running within Webpack. For example:

const externals = fs
  .readdirSync(modeModules)
  .filter(x => !/\.bin|react-universal-component|webpack-flush-chunks/.test(x))
  .reduce((externals, mod) => {
    externals[mod] = `commonjs ${mod}`
    return externals
  }, {})

AutoDllPlugin

Since the webpack plugin API does not yet allow you to add to stats, the AutoDllPlugin cannot add chunks to stats. Therefore you have to embed its corresponding script manually.

Low-level API: flushFiles

For advanced users that want access to all files flushed (.js, .css or whatever else might be in there) and without named entry chunks you already know (such as bootstrap, vendor, and main), here you go:

import { flushChunkNames } from 'react-universal-component/server'
import { flushFiles } from 'webpack-flush-chunks'

const chunkNames = flushChunkNames()
const scripts = flushFiles(stats, { chunkNames, filter: 'js' })
const styles = flushFiles(stats, { chunkNames, filter: 'css' })

i.e. this will get you all files corresponding to flushed "dynamic" chunks.

The only thing different with the API is that it has a filter option, and that it doesn't have before, after and outputPath options. The filter can be a file extension as a string, a regex, or a function: filter: file => file.endsWith('js').

Keep in mind, you will have to get right placing these between your bootstrap and main scripts. OR if you don't have a bootstrap script, you need to set it up so your main script doesn't actually call ReactDOM.render, and instead you put <script>window.render()</script> (where window.render() calls ReactDOM.render) after all your chunks in your markup so that by the time it's called all your chunks are loaded. In the latter case, you should put your dynamic chunks received from flushFiles after your main script so that the webpack bootstrap code now within your main script (as it regularly is) knows what to do with the additional scripts from dynamic chunks.

If what you want, instead of file names, is full-on compilation chunk objects (and any information it contains, which for 99% of most projects is unnecessary), create an issue and we'll add it. But until there is an actual need, we would like to keep the API simple.

Additional Docs

Universal Demo

🍾🍾🍾 faceyspacey/universal-demo πŸš€πŸš€πŸš€

git clone https://github.com/faceyspacey/universal-demo.git
cd universal-demo
yarn
yarn start

Contributing

We use commitizen, so run npm run cm to make commits. A command-line form will appear, requiring you answer a few questions to automatically produce a nicely formatted commit. Releases, semantic version numbers, tags, changelogs and publishing to NPM will automatically be handled based on these commits thanks to semantic-release. Be good.

Tests

Reviewing a package's tests are a great way to get familiar with it. It's direct insight into the capabilities of the given package (if the tests are thorough). What's even better is a screenshot of the tests neatly organized and grouped (you know the whole "a picture says a thousand words" thing).

Below is a screenshot of this module's tests running in Wallaby ("An Integrated Continuous Testing Tool for JavaScript") which everyone in the React community should be using. It's fantastic and has taken my entire workflow to the next level. It re-runs your tests on every change along with comprehensive logging, bi-directional linking to your IDE, in-line code coverage indicators, and even snapshot comparisons + updates for Jest! I requestsed that feature by the way :). It's basically a substitute for live-coding that inspires you to test along your journey.

webpack-flush-chunks wallaby tests screenshot

More from FaceySpacey in Reactlandia

  • redux-first-router. It's made to work perfectly with Universal. Together they comprise our "frameworkless" Redux-based approach to what Next.js does (splitting, SSR, prefetching, and routing). People are lovin it by the way 😎

webpack-flush-chunks's People

Contributors

andersdjohnson avatar chrisblossom avatar dashukin avatar davidfurlong avatar faceyspacey avatar forabi avatar greenkeeper[bot] avatar mikebars avatar samijaber avatar scriptedalchemy avatar swernerx avatar tcudok-jg avatar tstirrat15 avatar tusbar avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

webpack-flush-chunks's Issues

Duplicate CSS files while flushing

I am migrating a project to use flush chunks. Unfortunately I see duplicate entries for my main CSS assets being generated:

import flushChunks from 'webpack-flush-chunks'
const chunks = flushChunks(clientStats, {
    // TODO: This part should be dynamic later on based on the#
    // chunks which were rendered with the render call just before.
    chunkNames: [ "main" ]
})

When using

return `<head>
  <meta charset="utf-8">
  ${helmet.title.toString()}
  ${helmet.meta.toString()}
  ${helmet.link.toString()}
  ${chunks.styles.toString()}
</head>`

I see the following style rules being added to the head:

<link rel="stylesheet" href="/static/chunk-vendor-eeuzoICW.css">
<link rel="stylesheet" href="/static/chunk-vendor-eeuzoICW.css">
<link rel="stylesheet" href="/static/chunk-vendor-eeuzoICW.css">

This is with Webpack v4, a single "entry", and split chunks instructions for vendor and runtime:

    return {
        mode: isDev ? "development" : "production",
        stats: "minimal",

        entry: [
            resolve("src/@browser/index.js")
        ],

        // You may want 'eval' (in dev) instead if you prefer to see the compiled output in DevTools.
        // See the discussion in https://github.com/facebook/create-react-app/issues/343
        devtool: isDev ? "cheap-module-source-map" : "source-map",

        output: {
            path: resolve(distDirectory),
            publicPath: '/static/',
            hashFunction: Hasher,
            hashDigest: "base52", // A-Za-z
            hashDigestLength: 8,
            filename: isDev ? "bundle-[name].js" : "bundle-[name]-[chunkhash].js",
            chunkFilename: isDev ? "chunk-[name].js" : "chunk-[name]-[chunkhash].js",
            crossOriginLoading: "anonymous"
        },

        optimization: {
            minimize: !isDev,
            runtimeChunk: true,
            splitChunks: {
                cacheGroups: {
                    commons: {
                        test: /[\\/]node_modules[\\/]/,
                        name: 'vendor',
                        chunks: 'all'
                    }
                }
            },
        ....

flushChunks(stats, { chunkNames })

Hi!
Thanks for this an extremely useful packages!
Please, explain, what is stats and how to get them for:

const { js, styles, cssHash } = flushChunks(stats, {
  chunkNames: flushChunkNames()
})

chunkNames - ***array of chunks flushed from react-universal-component
is it clients chunks?
this should be the paths to the files, which is created by Universal Component with webpack-flush-chunks?

'app' of undefined

Hi,

I have this message in my terminal:

UnhandledPromiseRejectionWarning: Unhandled promise rejection (rejection id: 1): TypeError: Cannot read property 'app' of undefined
const chunkNames = flushChunkNames();
const { js, styles, cssHash } = flushChunks(assets, { chunkNames, before: [], after: ['app'] });

I'm doing something wrong?

Thk

flushFiles return empty []

Hi @faceyspacey ! This is AWESOME! Cannot thank you enough.
I was, before Chrome Canary droped dead at osx, trying to get a perfect score at the Dev tools Audit in this PWA website that lives at here at github. One of the problems, at the Performance section, is that it yells about chaining requests , even though I am using defer and async properly in each script I load. I am still loading/vendoring react, material-ui, rxjs and firebase which is like 200kb total. That I really rather inject into html instead of making the client having to deal with fetching this separately. Currently my html weight around 400kb with is low for chrome standards.

I am using 60frames/webpack-hot-server-middleware so its pretty easy to get thestats

server middleware

export default function serverRenderer(props) {
    return (req, res, next) => {
        const store = configureStore()

        res.send("<!DOCTYPE html>" +
            renderToStaticMarkup(
                <HTML
                    {...props}
                    userAgent={req.headers["user-agent"]}
                    url={req.url}
                    store={store}
                />))
    }
}

My html

export const Vendor = ({ path, defer = false, async = false, isProduction }) => {
    return (isProduction ?
        <script type={"text/javascript"} defer={defer} async={async} src={path} /> : null)
}
export const HTML = ({ clientStats, serverStats, production, userAgent, url, store}) => {
    const App = Renderer(url, userAgent, store) // calls renderToString
    const { js, styles } = flushChunks(clientStats, {
        moduleIds: flushModuleIds()
    })
    console.log(js.toString())
    const chunkNames = flushChunkNames()
    const scripts = flushFiles(serverStats, { chunkNames, filter: "js" })
    console.log(scripts)
    return (
        <html>
            <head>
                {production ? <link rel="manifest" href="/icons/manifest.json" /> : null}
                <style type="text/css"> {App[1].join(" ")} </style>
            </head>
            <body>
                <div id="root" dangerouslySetInnerHTML={{ __html: App[0] }} />
                <Vendor async={false} path={"/js/dev.js"} isProduction={!production} />
                <Vendor defer={true} path={"/js/client.js"} isProduction={!production} />

                <Vendor path={"/vendor/react.js"} isProduction={production} />
                <Vendor path={"/vendor/firebase.js"} isProduction={production} />
                <Vendor path={"/vendor/rxjs.js"} isProduction={production} />
                <Vendor path={"/vendor/material.js"} isProduction={production} />
                <Vendor defer={true} path={"/js/client.js"} isProduction={production} />

            </body>
        </html>)

js.toString() logs <script type='text/javascript' src='http://localhost:4000/js/vendor.js'></script> Which seems correct hence this is the only other chunk I have an an entry.
And PERFECT because now I can use a hash at the outputs names. That itself just made my day.

But thats not the rabbit I was aiming for. flushFiles returns empty. What else do I need to get it to work? Ideally I want to have access to chunk that have been tranformed by CommonChunks Plugin at the client webpack compilation from the server?
Please help. Do I need a bootstrap chunk?

webpack config

const LOADERS_OPTIONS = (env)=> [
    new webpack.NamedModulesPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.optimize.ModuleConcatenationPlugin(),
    new FaviconsWebpackPlugin({
	prefix: 'icons/',
	logo: './shared/icon/favicon.png',
	config: {
	    appName: "acharh",
	    lang: "pt",
	    start_url: '/?pwa=true',
	    theme_color: '#00bfff'
	}
    }),
    new webpack.LoaderOptionsPlugin({
	minimize: env.production,
	debug: true,
	cache: true,
	options: {
	    context: '/'
	}
    })
];
const SERVER_PLUGINS = (env) => [...LOADERS_OPTIONS(env)];
const CLIENT_PLUGINS = env => {
    let og = LOADERS_OPTIONS(env);

    if (env.production){
	og.push(
	    new webpack.optimize.CommonsChunkPlugin({
		name: "react-vendor",
		filename: "vendor/react.js",
		chunks: ["client", "react"]
	    }),
	    new webpack.optimize.CommonsChunkPlugin({
		name: "rxjs-vendor",
		filename: "vendor/rxjs.js",
		chunks: ["client", "rxjs"]
	    }),
	    new webpack.optimize.CommonsChunkPlugin({
		name: "material-vendor",
		filename: "vendor/material.js",
		chunks: ["client", "material"]
	    }),
	    new webpack.optimize.CommonsChunkPlugin({
		name: "firebase-vendor",
		filename: "vendor/firebase.js",
		chunks: ["client", "firebase"]
	    })
	);
    } else {
	og.push(
	    new webpack.optimize.CommonsChunkPlugin({
		name: "dev",
		filename: "js/dev.js", chunks: ["client", "vendor"]
	    })	);
    }
    return og;
};

[FLUSH CHUNKS]: Unable to find pages-Home in Webpack chunks. Please check usage of Babel plugin.

I'm receiving this error after updating to Webpack 4 and updated the Universal modules:

[FLUSH CHUNKS]: Unable to find pages-Home in Webpack chunks. Please check usage of Babel plugin.

My page no longer loads the page specific .js file. (Used to load <script type="text/javascript" src="/page/Home.js" defer="defer"></script>)

This is what shows up in my HTML now:

<script type="text/javascript" src="/static/bootstrap.js" defer="defer"></script>
<script type="text/javascript" src="/static/vendor.js" defer="defer"></script>
<script type="text/javascript" src="/static/main.js" defer="defer"></script>

The page successfully loads, but it is without javascript. What have I done wrong?

client.dev.js

const path = require('path')
const webpack = require('webpack')
const WriteFilePlugin = require('write-file-webpack-plugin')
const AutoDllPlugin = require('autodll-webpack-plugin')

module.exports = {
  name: 'client',
  mode: 'development',
  target: 'web',
  devtool: 'cheap-module-source-map',
  entry: [
    '@babel/polyfill',
    'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=false&quiet=false&noInfo=false',
    path.resolve(__dirname, '../../src/index.js')
  ],
  output: {
    filename: '[name].js',
    chunkFilename: '[name].js',
    path: path.resolve(__dirname, '../../buildClient'),
    publicPath: '/static/'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.(ttf|woff|woff2)$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          options: {
            outputPath: 'assets/'
          }
        }
      },
      {
        test: /\.(png|jpg|gif)$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          options: {
            outputPath: 'assets/'
          }
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.ttf', '.woff', '.woff2']
  },
  optimization: {
    runtimeChunk: {
      name: 'bootstrap'
    },
    splitChunks: {
      chunks: 'initial',
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: 'vendor',
          priority: -10
        }
      }
    }
  },
  plugins: [
    new WriteFilePlugin(), // See what chunks are produced in `dev`
    new webpack.HotModuleReplacementPlugin(),
    new webpack.NoEmitOnErrorsPlugin(),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('development')
      }
    }),
    new AutoDllPlugin({
      context: path.join(__dirname, '../..'),
      filename: '[name].js',
      entry: {
        vendor: [
          'react',
          'react-dom',
          'react-redux',
          'redux',
          'history/createBrowserHistory',
          'redux-first-router',
          'redux-first-router-link',
          '@babel/polyfill',
          'redux-devtools-extension/logOnlyInProduction'
        ]
      }
    })
  ]
}

server.dev.js

const fs = require('fs')
const path = require('path')
const webpack = require('webpack')
const WriteFilePlugin = require('write-file-webpack-plugin')

const res = p => path.resolve(__dirname, p)

// If you're specifying externals to leave unbundled, you need to tell Webpack
// to still bundle `react-universal-component`, `webpack-flush-chunks` and
// `require-universal-module` so that they know they are running within Webpack
// and can properly make connections to client modules:
const externals = fs
  .readdirSync(res('../../node_modules'))
  .filter(
    x =>
      !/\.bin|react-universal-component|require-universal-module|webpack-flush-chunks/.test(
        x
      )
  )
  .reduce((externals, mod) => {
    externals[mod] = `commonjs ${mod}`
    return externals
  }, {})

module.exports = {
  name: 'server',
  mode: 'development',
  target: 'node',
  devtool: 'cheap-module-source-map',
  entry: [
    '@babel/polyfill',
    res('../../server/render.js')
  ],
  externals,
  output: {
    path: res('../../buildServer'),
    filename: '[name].js',
    libraryTarget: 'commonjs2',
    publicPath: '/static/'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: {
          loader: 'babel-loader'
        }
      },
      {
        test: /\.(ttf|woff|woff2)$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          options: {
            outputPath: 'assets/'
          }
        }
      },
      {
        test: /\.(png|jpg|gif)$/,
        exclude: /node_modules/,
        use: {
          loader: 'file-loader',
          options: {
            outputPath: 'assets/'
          }
        }
      }
    ]
  },
  resolve: {
    extensions: ['.js', '.ttf', '.woff', '.woff2']
  },
  plugins: [
    new WriteFilePlugin(),
    new webpack.optimize.LimitChunkCountPlugin({
      maxChunks: 1
    }),
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify('development')
      }
    })
  ]
}

Docs request for combination with vendor chunks and or AutoDLL Plugin

I am currently trying to add the AutoDll Plugin to my build chain as it was reported before to work with this solution. Unfortunately I had no direct success. The vendor entry point does not seem to be visible for this plugin. It just lists the default set of bootstrap + dynamic + main. But no vendor. Is there any magic trick or option to utilize?

flushChunks not returns shared css between modules

Hi!

Awesome library, SSR + Code Splitting is a very common problem and you resolved it very well here :)

I would like to integrate it to an already production project, but we have an issue with the css files shared between modules.

For example, I have a Home module, and when I navigates to it to render synchronously, the chunkNames array is

[ 'Home', 'searchForm' ],

When I execute flushChunks:

  const { scripts, stylesheets } = flushChunks(stats, {
    chunkNames,
  });

This is the result:

[ 'js/Home.717e415a5908f7599b0a.chunk.js', 'js/searchForm.876649092bef6d33b3e8.chunk.js', 'js/main.ad7ec855eb95d76053ca.js' ] [ 'css/main.9eb3c8d1.css', 'css/Home.72db2c6e.chunk.css', 'css/searchForm.6a9b3cfc.chunk.css' ]

The thing is that when the browser loads the page, it gets some others js and css files on demand, but those css files are needed in the first paint, because otherwise generates a weird user experience changing width and height of items, among others things :(.

The other files I am talking about in this case are:

['css/default~Home~addressForm~checkoutRouter~landing~myAddresses~myOrders~shopDetailsRouter~shopsList.9eb5c300.chunk.css', 'css/default~Home~addressForm~checkoutRouter~landing~myOrders~shopDetailsRouter~shopsList.857b76b4.chunk.css']

It seems that those files are shared between modules (all those are module names).
Is there any way to get those files in the flushChunks call?

If you need more details please let me know.

Thanks!

Using with renderToNodeStream

Is there a way to use this with renderToNodeStream from react 16?
It seems we need to flush chunks after SSR, but we also need to inject styles into head. So not possible?

webpack v5: missing dynamic chunks generated by `splitChunks` when chunk IDs are strings

Full reduced test case: https://github.com/OliverJAsh/webpack-flush-chunks-webpack-v5-bug

package.json:

{
  "dependencies": {
    "react": "^17.0.2",
    "react-dom": "^17.0.2",
    "webpack": "^5.58.0",
    "webpack-cli": "^4.9.0",
    "webpack-flush-chunks": "^2.0.3"
  }
}

webpack.config.js:

module.exports = {
    mode: "production",
    entry: "./src/entry.js",
    optimization: {
        chunkIds: 'named',
    }
};

src/entry.js:

import(/* webpackChunkName: "routeA" */ './routeA')
import(/* webpackChunkName: "routeB" */ './routeB')

src/routeA.js:

import React from 'react';
import ReactDOM from 'react-dom';
React;
ReactDOM;

src/routeB.js:

import React from 'react';
import ReactDOM from 'react-dom';
React;
ReactDOM;

test.js:

const flushChunks = require("webpack-flush-chunks").default;

const stats = require("./stats.json");

const result = flushChunks(stats, { chunkNames: ["routeA"] });

console.log(result.scripts);

Run these commands:

rm -rf dist
webpack --json=stats.json
node test.js

Expected result:

[ 'routeA.js', 'main.js' ]

Actual result:

[
  'vendors-node_modules_react-dom_index_js.js',
  'routeA.js',
  'main.js'
]

flushChunkNames() returning empty array with base reactQL setup

Thanks so much for your work. This is potentially the perfect solution for us.

I am setting up an app based on the boilerplate reactQL - on the base install, I have set up all the necessary components of react-universal-component on the client and server.

I now have imports that get compiled down to this:

var UniversalComponent = __WEBPACK_IMPORTED_MODULE_4_react_universal_component___default()(function (props) {
  return __WEBPACK_IMPORTED_MODULE_2_babel_plugin_universal_import_universalImport_js___default()({
    id: './',
    file: '/Users/shomanishikawa/Desktop/app2/src/components/Home.jsx',
    load: function load() {
      return Promise.all([__webpack_require__(362)("./" + props.page), __WEBPACK_IMPORTED_MODULE_1_babel_plugin_universal_import_importCss_js___default()('' + props.page)]).then(function (proms) {
        return proms[0];
      });
    },
    path: function path() {
      return __WEBPACK_IMPORTED_MODULE_0_path___default.a.join(__dirname, './' + props.page);
    },
    resolve: function resolve() {
      return /*require.resolve*/(__webpack_require__(363).resolve("./" + props.page));
    },
    chunkName: function chunkName() {
      return '' + props.page;
    }
  });
});

when you navigate to this page, you correctly get the bundle as a separate request. On the SSR though, both the client and server first initialize in their 'loading...' state. So I have moved on to implementing webpack-flush-chunks.

this is a segment of the server render:

export function createReactHandler(css = [], scripts = [], chunkManifest = {}) {
  return async function reactHandler(ctx) {
    const routeContext = {};

    // Create a new server Apollo client for this request
    const client = serverClient();

    // Create a new Redux store for this request
    const store = createNewStore(client);

    // Generate the HTML from our React tree.  We're wrapping the result
    // in `react-router`'s <StaticRouter> which will pull out URL info and
    // store it in our empty `route` object
    const components = (
      <StaticRouter location={ctx.request.url} context={routeContext}>
        <ApolloProvider store={store} client={client}>
          <App />
        </ApolloProvider>
      </StaticRouter>
    );

    // Wait for GraphQL data to be available in our initial render,
    // before dumping HTML back to the client
    await getDataFromTree(components);

    // Full React HTML render
    const html = ReactDOMServer.renderToString(components);

    console.log(flushChunkNames());

flushChunkNames() always returns an empty array, even when loading a route that has to synchronously import a dynamic bundle (and the bundle does indeed get loaded dynamically after page load)

The stripped down example is here - it has the base reactQL install with the setup for react-universal-component.

Not sure exactly what the issue is, and would appreciate if you could point me in the right direction. Maybe some part of the existing reactQL setting is preventing the tracking of bundles?

Ability to add a nonce-attribute to dynamically generated script tags to comply with strict Content-Security-Policy

Hey :)
I'm doing a bit of security-hardening and wanted to switch to to a stricter content security policy (e.g. Google's Strict CSP3 Example).

This requires me adding nonce-attributes to all my script tags: <script nonce="random-base64-string">. I've managed to do this for almost all script tags.

For the js chunks exposed by webpack-flush-chunks I hacked my way there:

const myNonce = "someBase64String"
const { js: rawJs, styles, cssHash: rawCssHash } = flushChunks(
  clientStats,
  { chunkNames }
)
const js = rawJs.toString().replace('<script', `<script nonce="${myNonce}"`)
const cssHash = rawCssHash.toString().replace('<script', `<script nonce="${myNonce}"`)

However, I haven't managed to find a way to get these nonces into the dynamically loaded chunks.

According to the webpack docs it's simply a matter of setting __webpack_nonce__ = 'someBase64String'; at the top of my entry file (the render.js from the redux-first-router-demo repo). But no luck. No matter where I set the webpack-nonce, it doesn't pick it up.

Wasn't sure whether that's more of a webpack-flush-chunks or babel-plugin-universal-import (or something else entirely?) responsibility so I open this issue here.

Any advice?

Raw CSS on development environment

I'm using webpack-flush-chunks version 2.0.3
I noticed these lines of code:

css: {
      toString: function toString() {
        return (
          // lazy-loaded in case not used
          DEV ? api.styles.toString() : '<style>' + stylesAsString(stylesheets, outputPath) + '</style>'
        );
      }
    }

Is there a particular reason why we are only showing raw CSS on non-DEV environment only? I just thought that it is better to return the raw CSS regardless of the environment. And use the exposed styles variable to get the linked stylesheets, the way it is now.
Doing this will make us see in dev as closely as possible with what we are going to see in production.

Thank you!

flushChunkNames is empty

Hi @faceyspacey
i am trying to render my component on server, a component which fetches the data from another 3rd party API on componentDidMount, my component is rendering fine but when is pass the string returned from ReactDOM.renderToString to my .ejs file which is missing the data returned from 3rd party API

import React from 'react';
import ReactDOM from 'react-dom/server';
import { flushChunkNames } from 'react-universal-component/server';
import flushChunks from 'webpack-flush-chunks';

import App from '../shared/App';
import HorizontalModule from '../shared/Horizontal-module';
export default ({ clientStats }) => async (req, res) => {
    const app = (
        <App/>
    );
 
    const appString = ReactDOM.renderToString(app);
    const chunkNames = flushChunkNames();
    const { js, styles, cssHash } = flushChunks(clientStats, { chunkNames });
    res.render('index', {
        appString,
        js,
        styles,
        cssHash
    });

    console.log(appString);
};

my component is rendering fine, but when is see the view-source of a page component is empty.
appString is basically empty

currently my code for fetching data from API written in componentDidMount but i have tried componentWillMount as well

and this is my code base https://github.com/sharmam1/testproj

Stylesheets in reversed order

CSS ordering is reversed as it would be if the files were not split into separate files.

<head>
  <link rel='stylesheet' href='/static/0.css' />
  <link rel='stylesheet' href='/static/7.css' />
  <link rel='stylesheet' href='/static/main.css' />
</head> 

The CSS files should be reversed:

<head>
  <link rel='stylesheet' href='/static/main.css' />
  <link rel='stylesheet' href='/static/7.css' />
  <link rel='stylesheet' href='/static/0.css' />
</head> 

ReactLoadable.flushRequires does not exist and { js, styles } contains <spans>

This appears to be wrong/outdated:

const moduleIds = ReactLoadable.flushRequires()

AFAICT, that's been replaced by either flushServerSideRequirePaths or flushwebpackRequireWeakIds, not sure which.

Secondly, these guys:

const { js, styles } = flushChunks(moduleIds, stats)

Wrap the output in spans. For example, I'm getting:

  <span id='__styles__'>
          <link rel='stylesheet' href='http://localhost:8041/main.css' />
        </span>
  <span id='__javascript__'>
          <script type='text/javascript' src='http://localhost:8041/bootstrap.no_css.js'></script>
<script type='text/javascript' src='http://localhost:8041/bundle.no_css.js'></script>
        </span>

I don't think these spans are necessary considering they're not React components, are they?

Also, rootDir appears to be required, even with webpack.

[FLUSH CHUNKS]: Unable to find pages-Landing in Webpack chunks. Please check usage of Babel plugin.

Hi, guys. I faced with this issue after I had updated webpack from 3-rd to 4-th version. The problem is that path is pages-Landing incorrect. After client build was created this page is placed as pages/Landing
Here is my webpack client.dev.js config

const path = require('path');
const webpack = require('webpack');
const WriteFilePlugin = require('write-file-webpack-plugin');
const ExtractCssChunks = require('extract-css-chunks-webpack-plugin');
const defineEnv = require('./dotenv');

module.exports = {
	mode: 'development',
	name: 'client',
	target: 'web',
	devtool: 'eval',
	entry: [
		'babel-polyfill',
		'fetch-everywhere',
		'webpack-hot-middleware/client?path=/__webpack_hmr&timeout=20000&reload=false&quiet=false&noInfo=false',
		'react-hot-loader/patch',
		path.resolve(__dirname, '../src/index.js'),
	],
	output: {
		filename: '[name].js',
		chunkFilename: '[name].js',
		path: path.resolve(__dirname, '../buildClient'),
		publicPath: '/static/',
	},
	module: {
		rules: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: 'babel-loader',
			},
			{
				test: /\.css$/,
				use: [
					{
						loader: ExtractCssChunks.loader,
					},
					{
						loader: 'css-loader',
						options: {
							importLoaders: 1,
							modules: {
								localIdentName: '[name]__[local]--[hash:base64:5]',
							},
						},
					},
					'postcss-loader',
				],
			},
			{
				test: /\.(png|jpg|gif)$/,
				use: [
					{
						loader: 'file-loader',
					},
				],
			},
		],
	},
	resolve: {
		extensions: ['.js', '.css'],
	},
	plugins: [
		new WriteFilePlugin(),
		new ExtractCssChunks({
			filename: '[name].css',
			chunkFilename: '[name].css',
		}),
		new webpack.HotModuleReplacementPlugin(),
		new webpack.NoEmitOnErrorsPlugin(),
		new webpack.DefinePlugin(defineEnv),
	],
	optimization: {
		splitChunks: {
			cacheGroups: {
				vendor: {
					test: /node_modules/,
					chunks: 'initial',
					name: 'vendor',
					enforce: true,
				},
			},
		},
	},
};

Here is server.dev.js config file

const fs = require('fs');
const path = require('path');
const webpack = require('webpack');
const defineEnvironment = require('./dotenv');

const res = p => path.resolve(__dirname, p);
const devMode = process.env.NODE_ENV !== 'production';

// if you're specifying externals to leave unbundled, you need to tell Webpack
// to still bundle `react-universal-component`, `webpack-flush-chunks` and
// `require-universal-module` so that they know they are running
// within Webpack and can properly make connections to client modules:
const externals = fs
	.readdirSync(res('../node_modules'))
	.filter(
		x => !/\.bin|react-universal-component|require-universal-module|webpack-flush-chunks/.test(x),
	)
	.reduce((ext, mod) => {
		ext[mod] = `commonjs ${mod}`; // eslint-disable-line no-param-reassign
		return ext;
	}, {});

module.exports = {
	mode: devMode ? 'development' : 'production',
	name: 'server',
	target: 'node',
	devtool: 'source-map',
	entry: ['babel-polyfill', 'fetch-everywhere', res('../server/render.js')],
	externals,
	output: {
		path: res('../buildServer'),
		filename: '[name].js',
		libraryTarget: 'commonjs2',
		publicPath: '/static/',
	},
	module: {
		rules: [
			{
				test: /\.js$/,
				exclude: /node_modules/,
				use: 'babel-loader',
			},
			{
				test: /\.(sa|sc|c)ss$/,
				exclude: /node_modules/,
				use: [
					{
						loader: 'css-loader',
						options: {
							importLoaders: 1,
							modules: {
								exportOnlyLocals: true,
								localIdentName: '[name]__[local]--[hash:base64:5]',
							},
						},
					},
				],
			},
			{
				test: /\.(png|jpg|gif)$/,
				use: [
					{
						loader: 'file-loader',
					},
				],
			},
		],
	},
	resolve: {
		extensions: ['.js', '.css'],
	},
	plugins: [
		new webpack.optimize.LimitChunkCountPlugin({ maxChunks: 1 }),
		new webpack.DefinePlugin(defineEnvironment),
	],
};

I hope you guys can help me with that.

0.2.1 Error: Cannot read property 'reduce' of undefined

After updating to v0.2.1 the following error occurs. I tested this against the flush-chunks-boilerplate repo and it is happening there as well.

TypeError: Cannot read property 'reduce' of undefined
    at createFilesByPath (/Users/chris/github/app/build/webpack:/~/webpack-flush-chunks/dist/flushChunks.js:101:1)
    at createFilesByModuleId (/Users/chris/github/app/build/webpack:/~/webpack-flush-chunks/dist/flushChunks.js:116:1)
    at flushWebpack (/Users/chris/github/app/build/webpack:/~/webpack-flush-chunks/dist/flushChunks.js:90:1)
    at flush (/Users/chris/github/app/build/webpack:/~/webpack-flush-chunks/dist/flushChunks.js:70:1)
    at flushChunks (/Users/chris/github/app/build/webpack:/~/webpack-flush-chunks/dist/flushChunks.js:37:1)
    at module.exports.../node_modules/webpack-flush-chunks/dist/flushChunks.js.exports.default (/Users/chris/github/app/build/webpack:/~/webpack-flush-chunks/dist/flushChunks.js:29:1)
    at Html (/Users/chris/github/app/build/webpack:/src/server.js:85:1)
    at /Users/chris/github/app/node_modules/react-dom/lib/ReactCompositeComponent.js:306:16
    at measureLifeCyclePerf (/Users/chris/github/app/node_modules/react-dom/lib/ReactCompositeComponent.js:75:12)
    at ReactCompositeComponentWrapper._constructComponentWithoutOwner (/Users/chris/github/app/node_modules/react-dom/lib/ReactCompositeComponent.js:305:14)

I tried to create a test case but was unsuccessful, sorry.

52 Vulnerabilities in NPM modules

When doing a raw npm install, there is a warning of 52 vulnerabilities.

found 52 vulnerabilities (33 low, 6 moderate, 13 high) in 14042 scanned packages
  43 vulnerabilities require semver-major dependency updates.
  9 vulnerabilities require manual review. See the full report for details.

Looks like some can be automatically fixed, but the upgrade versions break SEMVER, so might need to be careful.

Add support for SRI like in Webpack-Html-Plugin

It would be nice when the plugin would be able to use the integrity information added to assets by the SRI plugin: https://www.npmjs.com/package/webpack-subresource-integrity.

This section might be helpful for implementation:
https://www.npmjs.com/package/webpack-subresource-integrity#without-htmlwebpackplugin

With this is place it would be safe to push all these bundles onto a CDN as the browser checks whether the SRI checksums are matching and the correct files are being delivered.

[FLUSH CHUNKS] warning with TSX, but not with JS

Hello, I get this warning:
[FLUSH CHUNKS]: Unable to find styles/localhost-theme-css in Webpack chunks. Please check usage of Babel plugin.

Following code causes the warning (for setup of react-universal-component):

export default (props) => {
  if (props.site !== undefined) {
    import(`../styles/${props.site}/theme.css`);
  }

Above code is in Routes.tsx, whole file looks like:

import React from "react"
import universal from "react-universal-component"
import { Switch } from "react-router"

const determineHowToLoad = ({ page }) => {
  if (typeof page !== 'string') {
    return page();
  } else {
    return import(`./${page}`);
  }
}

const UniversalComponent = universal(determineHowToLoad, {
  loadingTransition: false
})

export default (props) => {
  if (props.site !== undefined) {
    import(`../styles/${props.site}/theme.css`);
  }

  return (
    <div>
      Test
    </div>
  )
}

However, this happens only if when the filename is Routes.tsx. If I change to Routes.js, no warning occurs. Even with the warning and filename being Routes.tsx, all the things looks working well but only warning occurs in console terminal.

My webpack setting:
1. webpack.dev-client.js:

optimization: {
    splitChunks: {
      chunks: "initial",
      cacheGroups: {
        vendors: {
          test: /[\\/]node_modules[\\/]/,
          name: "vendor"
        }
      }
    }
  },
  devtool: "source-map",
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader"
          }
        ]
      },
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "ts-loader"
          }
        ]
      },
      {
        test: /\.(js|jsx)$/,
        use: 'react-hot-loader/webpack',
        include: /node_modules/
      },
      {
        test: /\.css$/,
        use: [
          ExtractCssChunks.loader, "css-loader",
        ]
      },
....
resolve: {
    extensions: [".ts", ".tsx", ".js", ".css"]
  },

2. webpack.dev-server.js:

devtool: "inline-source-map",
  module: {
    rules: [
      {
        test: /\.js$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "babel-loader"
          }
        ]
      },
      {
        test: /\.(ts|tsx)$/,
        exclude: /node_modules/,
        use: [
          {
            loader: "ts-loader"
          }
        ]
      },
      {
        test: /\.css$/,
        use: [
          ExtractCssChunks.loader, "css-loader"
        ]
      },
....
 resolve: {
    extensions: [".ts", ".tsx", ".js", ".css"]
  },

How can I solve without using js?

Webpack/Babel Absolute Path is making FLUSH CHUNKS error

When using absolute path resolve like

import React from "react";
import universal from "react-universal-component";

const UniversalImport = universal(
  ({ page }) => {
    return import(`pages/${page}/${page}`);
  },
  {
    onLoad(module, info, props, context) {
      if (module.reducers) {
        context.store.injectReducers(module.reducers);
      }
    }
  }
);

export default UniversalImport;

Error:

[FLUSH` CHUNKS]: Unable to find pages/Login-Login in Webpack chunks. Please check usage of Babel plugin.

This error occurs when using webpack resolve or babel-plugin-module-resolver.

v2 includes runtimeChunk twice with optimization.runtimeChunk option in webpack v4

I noticed that my JS was running twice after upgrading to [email protected] when using optimization.runtimeChunk option in webpack v4.

My runtimeChunk bootstrap.js gets included twice in the html

<script type='text/javascript' src='/static/bootstrap.js' defer></script>
<script type='text/javascript' src='/static/Foo.js' defer></script>
<script type='text/javascript' src='/static/bootstrap.js' defer></script>
<script type='text/javascript' src='/static/main.js' defer></script>

I've confirmed that this no longer happens if I remove optimization.runtimeChunk from the webpack config or switch back to [email protected].

Example Repo

https://github.com/akash5474/universal-demo/tree/bug-demo

Changes Made

Forked faceyspacey/universal-demo and made the following changes (akash5474/universal-demo@764c1be and akash5474/universal-demo@fd65e57):

  1. Added a console.log("hello world") statement to src/index.js to demonstrate the issue.

  2. Updated to the following dependency versions:

"webpack-flush-chunks": "2.0.0",
"extract-css-chunks-webpack-plugin": "3.0.5",
"babel-plugin-universal-import": "3.0.0",
"webpack": "4.12.0",
  1. Updated webpack configs to work with new dependency versions

  2. Deleted some unnecessary code

Steps to reproduce

git clone -b bug-demo https://github.com/akash5474/universal-demo

cd universal-demo

npm install

npm run start:prod

Open http://localhost:3000 in the browser and open the console.

You will see "hello world" is logged twice.

Viewing the source html you will see bootstrap.js is included twice

This can be resolved by removing the following from webpack client config:

  optimization: {
    runtimeChunk: {
      name: "bootstrap"
    },
  }

However after downgrading to [email protected], bootstrap.js is only included once with the optimization.runtimeChunk option included. This suggests that the problem was introduced in [email protected]

Let me know if you have any questions or if I can help in any way. Thanks.

Webpack 4

I'm pretty new to React, and event more so to webpack. I wanted to use your demo as bootstrap code for an application that I'm working, but hoped I could upgrade to Webpack 4.
Once I did that, I received a number of errors around the use of CommonsChunkPlugin, which it seems no longer exists. It didn't look to me like the recommended migration would work, and the little bits of info I found on google seemed to agree.

Do you have these components working with Webpack 4, or know where's there's any info that could help. To be honest, it wouldn't be the end of the world if I had to roll back to 3.x, but worth asking.

Cheers
Chris

Dependencies of react

I was trying to use this amazing tool with require-universal-module in an no react environment. but apparently this library is react dependant.

ERROR in (webpack)-flush-chunks/dist/createApiWithCss.js
    Module not found: Error: Can't resolve 'react' in 'C:\Users\bernardosunderhus\Desktop\pextra\node_modules\webpack-flush-chunks\dist'
     @ (webpack)-flush-chunks/dist/createApiWithCss.js 8:13-29
     @ (webpack)-flush-chunks/dist/flushChunks.js
     @ ./src/node/index.js

Is there any plans to make this work without this dependency?

Action required: Greenkeeper could not be activated 🚨

🚨 You need to enable Continuous Integration on all branches of this repository. 🚨

To enable Greenkeeper, you need to make sure that a commit status is reported on all branches. This is required by Greenkeeper because we are using your CI build statuses to figure out when to notify you about breaking changes.

Since we did not receive a CI status on the greenkeeper/initial branch, we assume that you still need to configure it.

If you have already set up a CI for this repository, you might need to check your configuration. Make sure it will run on all new branches. If you don’t want it to run on every branch, you can whitelist branches starting with greenkeeper/.

We recommend using Travis CI, but Greenkeeper will work with every other CI service as well.

Chunk dependencies not flushed when it's moved to a parent

There's this flag reuseExistingChunk of SplitChunksPlugin which, when true, moves a chunk up to the parent cache group to avoid outputting duplicated code, and doesn't generate any file for the source chunk itself. This conflicts with the behaviour of flushChunkNames() since it won't look for chunk dependencies inside stats.namedChunkGroups if it's not inside stats.chunks in the first place, but it should. More people might face this same issue because it's the default behaviour of splitChunks.cacheGroups.defaults.

To be honest, I'm not 100% sure if it's because of reuseExistingChunk. Maybe this is just the expected normal behaviour.

Scenarios:

  • User has defined a cache group but it only encompasses one module, meaning the group chunk will be generated but not a chunk for the source module
  • User has defined a cache group but, because of restrictions provided by minChunks, minSize, maxSize or any other config parameter, Webpack ends up moving a module to the parent chunk

Related code fragment:

if (!hasChunk(name, stats.assetsByChunkName, checkChunkNames)) {
return names
}
const files = filesByChunkName(name, stats.namedChunkGroups)
names.push(...files)
return names

reference script file outside of webpack build

I am server rendering my react app like this:

export default ({ clientStats }: { clientStats: any }) => async (req: Request, res: Response, next: any) => {
  const context: any = {};

  const app = (
    <StaticRouter location={req.url} context={context}>
      <Application />
    </StaticRouter>
  );

  if (context.url) {
    res.writeHead(301, {
      Location: context.url
    });

    res.end();
    return;
  }

  const { styles, js, scripts } = flushChunks(clientStats, {
    chunkNames: flushChunkNames()
  });

  const appString = renderToString(app);
  const { title } = Helmet.renderStatic();

  res.status(200).send(`
    <!doctype html>
    <html lang="en">
      <head>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <meta httpEquiv="X-UA-Compatible" content="IE=edge" />
        <meta charSet="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        ${styles}
        ${title}
      </head>
      <body>
        <div id="root">${appString}</div>
        <script type=\'text/javascript\' src=\'init.js\' defer></script>
      </body>
    </html>
`);
};

How can I reference a script that is outside of the webpack build?

I have an init.js file that does very little and I want to just reference it. I don't need it transpiled or anything.

Where can I put it so that when the html is rendered, the script tag resolves?

flushChunks - cssHash only providing 1 of 2 CSS chunks consumed by any JS chunk

Hi @faceyspacey

We are using UniversalComponent with webpack-flush-chunks.

Because our project is a mix of SASS and PostCSS, we have 2 instances of ExtractCssChunks - one for SASS transpilation, and one for PostCSS transpilation. In the end, for many components, webpack creates 2 CSS files with the formats [name]-post-[chunkhash:6].css & [name]-[chunkhash:6].css.

When I check windows.__CSS_CHUNKS_, I can only see css files from the PostCSS transpilation.

So the outcome is, when a page rendered with UniversalComponent is loaded asynchronously on the client, only 1 of the 2 CSS files are loaded.

However, if I load the same page synchronously, both 2 of the 2 CSS files are loaded, and I can see these being loaded in the network tab in Devtools (as well as these filenames being provided in the stylesheets value returned from flushChunks).

Can you suggest a way for this to be remedied? Perhaps each key of the CSS hash needs to support an array of CSS files ?

Breaks when using public path on the fly

If you are using a public path on the fly, then the window.__CSS_CHUNKS__ maps to an incorrect URL as the publicPath does not exist in the stats object.

https://webpack.js.org/configuration/output/#output-publicpath
https://webpack.js.org/guides/public-path/#on-the-fly

I think the solution would be to detect if the stats file has the publicPath and if not then babel-plugin-universal-import's getHref function should manually append the __webpack_public_path__. It would probably be even cleaner if webpack-flush-chunks never used the public path and left it entirely up to babel-plugin-universal-import to use the __webpack_public_path__ but that would be a breaking change.

Happy to submit a PR if this generally makes sense.

Breaking changes between v1 and v2

I see the Significent changes to internal resolution system. note in the releases but it's somewhat unclear what the impact of that change is meant to be. Is v2 not compatible with webpack <= 3?

Usage when writing an npm package

We ran into a challenge last week that we couldn't solve with CSS Modules + webpack-flush-chunks. Maybe you have a recommendation here.

We have a core-ui repository with components we use in all of our apps like Button, TextInput, Logo etc.

We consume this repo in other apps as an npm module. Our goal is to be able to import Button from 'core-ui/Button' without any extra import 'core-ui/Button/styles.css' or a global import 'core-ui/styles.css'.

Haven't been able to get it working and had to resolve to using styled-components. We're not too unhappy because we'll most likely also consume the core components in a future react-native app where styled-components might offer some benefits over CSS Modules but I was still wondering if there is a way to achieve the above with your setup?

Unable to find component in folder

Hi,

I forked your redux first router demo repository and made my experimental app from it.
So i dont copy the config files because they are the same from your repository.

When i try to import a component with Universal like this :

const UniversalComponent = universal( props => import('./questions/${props.component}') );

This is the tree directory :

|-- UniversalComponent.js
|-- questions 
    |-- CheckSingle.jsx
    |-- Other components..

I've got the following error :
[FLUSH CHUNKS]: Unable to find questions/CheckSingle in Webpack chunks. Please check usage of Babel plugin.

I checked the stats.json and all the files in questions folder appear to be there.
If i used universal to load pages just like your demo it works nicely. But when i want to create a folder and import a component in it, this time it doesnt work.

I think there is something i dont understand.

What i am missing ?

utils.js:50 chunk not available for synchronous require yet:

i am using this boilerplate https://github.com/rherwig/template-react-16-ssr
getting the following error utils.js:50 chunk not available for synchronous require yet: for my multiple modules, could you please describe what could be the issue and how can i control my chunks size

Sample error

utils.js:50 chunk not available for synchronous require yet: ./src/shared/components/AhHeader.js: Module build failed (from ./node_modules/extract-css-chunks-webpack-plugin/dist/loader.js):
ModuleParseError: Module parse failed: Unexpected character '' (1:0)
You may need an appropriate loader to handle this file type.

(Source code omitted for this binary file)
    at handleParseError (/mnt/www/artifacts/x/5820/node_modules/webpack/lib/NormalModule.js:432:19)
    at doBuild.err (/mnt/www/artifacts/x.dev/5820/node_modules/webpack/lib/NormalModule.js:466:5)
    at runLoaders (/mnt/www/artifacts/x.dev/5820/node_modules/webpack/lib/NormalModule.js:327:12)
    at /mnt/www/artifacts/x.dev/5820/node_modules/loader-runner/lib/LoaderRunner.js:370:3
    at iterateNormalLoaders (/mnt/www/artifacts/x.dev/5820/node_modules/loader-runner/lib/LoaderRunner.js:211:10)
    at Array.<anonymous> (/mnt/www/artifacts/x.dev/5820/node_modules/loader-runner/lib/LoaderRunner.js:202:4)
    at Storage.finished (/mnt/www/artifacts/x.dev/5820/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:43:16)
    at provider (/mnt/www/artifacts/x.dev/5820/node_modules/enhanced-resolve/lib/CachedInputFileSystem.js:79:9)
    at /mnt/www/artifacts/x.dev/5820/node_modules/graceful-fs/graceful-fs.js:78:16
    at FSReqWrap.readFileAfterClose [as oncomplete] (fs.js:511:3) Error: Module build failed (from ./node_modules/extract-css-chunks-webpack-plugin/dist/loader.js):
ModuleParseError: Module parse failed: Unexpected character 'οΏ½' (1:0)
You may need an appropriate loader to handle this file type.
(Source code omitted for this binary file)

Inject other scripts between bootstrap and dynamic chunks.

@faceyspacey First off, Kudos!!! Really like your SSR based approaches.

Coming to the point, i have my codebase that has a vendor dll lib that produces a packed bundle on development mode. The flushChunks doesn't give me this lib name since it's only referenced in webpack bundling.

If i try to inject this before the bootstrap chunk i get checksum mismatch in the browser. Any help on how do i get around with this ? I also tried adding it in the before array that flushChunk accepts, but doesn't seem to recognize it.

Edit:
@faceyspacey: Irrespective of the order when dll script is included, i still get the checksum mismatch issue. That's probably because the dll has the react put into it. This problem won't be in production but i'm just curious as to why i get checksum mismatch with chunks in place.

Chunks not being created with TypeScript : [FLUSH CHUNKS]: Unable to find default in Webpack chunks. Please check usage of Babel plugin

I get this error in a Typescript project of mine. So I cloned the universal-demo project and changed things to typescript( except server/index.js ). The project builds successfully, and then I get the same error.
image
What am I missing here? The only changes I have made to the webpack configs is adding at-loader to the rules and including ts and tsx to resolve extensions.

Current project structure:
image
tsconfig.json
{
"compileOnSave": true,
"exclude": ["node_modules"],
"compilerOptions": {
"noStrictGenericChecks": true,
"allowJs": true,
"declaration": false,
"noResolve": false,
"jsx": "react",
"module": "commonjs",
"target": "es2015",
"experimentalDecorators": true,
"sourceMap": true,
"noImplicitAny": false,
"noImplicitReturns": true,
"outDir": "./dist/",
"skipLibCheck": true,
"allowSyntheticDefaultImports": true,
"moduleResolution": "node",
//"types": [ "node" ],
"typeRoots": []
}
}

sample : https://github.com/aneogiarcadix/universal-demo-ts

FlushChunks doesn't seen to work with JSON version of the stats-webpack-plugin

I've seen in the boilerplates that the way to have access to the stats in production is by producing a JSON version of it with the stats-webpack-plugin.

// in the webpack plugins
new StatsWriterPlugin({filename: 'stats/web.json'})

I produced this stats.json

{
  "assetsByChunkName": {
    "vendors": [
      "assets/js/vendors.js",
      "assets/js/vendors.js.map"
    ],
    "app": [
      "assets/js/app.js",
      "assets/css/app.css",
      "assets/css/style.css",
      "assets/js/app.js.map",
      "assets/css/app.css.map",
      "assets/css/style.css.map"
    ],
    "Sensors": [
      "assets/js/chunks/Sensors.js",
      "assets/js/chunks/Sensors.js.map"
    ],
    "Sensor": [
      "assets/js/chunks/Sensor.js",
      "assets/js/chunks/Sensor.js.map"
    ],
    "Notifications": [
      "assets/js/chunks/Notifications.js",
      "assets/js/chunks/Notifications.js.map"
    ],
    "Alerts": [
      "assets/js/chunks/Alerts.js",
      "assets/js/chunks/Alerts.js.map"
    ],
    "commons": [
      "assets/js/chunks/commons.js",
      "assets/js/chunks/commons.js.map"
    ],
    "manifest": [
      "assets/js/manifest.js",
      "assets/js/manifest.js.map"
    ]
  }
}

and eventually in my server side application I'll be calling:

// chunkNames = ['Sensor']
const stats = require('./stats.json')
const {js, styles} = flushChunks(stats, {chunkNames, before: ['manifest'], after: ['app']})

But unfortunatelly I'm receiving this Error, which points out to the line of code where I call flushChunks in the end.

TypeError: Cannot read property 'replace' of undefined at exports.default (C:\Users\bernardosunderhus\Desktop\pextra\node_modules\webpack-flush-chunks\dist\createApiWithCss.js:23:36) at flushChunks (C:\Users\bernardosunderhus\Desktop\pextra\node_modules\webpack-flush-chunks\dist\flushChunks.js:43:41) at exports.default (C:\Users\bernardosunderhus\Desktop\pextra\node_modules\webpack-flush-chunks\dist\flushChunks.js:29:10) at _callee$ (C:/Users/bernardosunderhus/Desktop/pextra/server/application/index.js:19:37) at tryCatch (C:\Users\bernardosunderhus\Desktop\pextra\node_modules\regenerator-runtime\runtime.js:65:40) at Generator.invoke [as _invoke] (C:\Users\bernardosunderhus\Desktop\pextra\node_modules\regenerator-runtime\runtime.js:303:22) at Generator.prototype.(anonymous function) [as next] (C:\Users\bernardosunderhus\Desktop\pextra\node_modules\regenerator-runtime\runtime.js:117:21) at step (C:\Users\bernardosunderhus\Desktop\pextra\server\application\index.js:26:191) at C:\Users\bernardosunderhus\Desktop\pextra\server\application\index.js:26:361

I'm currently using [email protected]

[question] Missing/wrong chunks with CommonChunksPlugin and dual-import

My configuration makes extensive use of the CommonsChunkPlugin to bundle common dependencies into async chunks. Unfortunately, this causes issues the babel-plugin-dual-import and this plugin, as the CSS files the modules within the commons chunk refer to are no longer in the location they assume, but within a combined commons chunk.

For example, if I have src/moduleA and src/moduleB, the importCSS call injected via babel-plugin-dual-import assumes that each of their stylesheets are in src/moduleA.css and src/moduleB.css respectively. However, when these are combined into an async chunk using CommonsChunkPlugin, say named common, both stylesheets now actually reside at common.css.

webpack-flush-chunks gives the "correct" output of these chunks when looking at the CSS Hash, but obviously since babel-plugin-dual-import has no knowledge of the output chunks, the stylesheets it refers to are not in the hash e.g.

var cssHashWithoutCommonsChunkPlugin = {
   "src/moduleA": "output/moduleA.css",
   "src/moduleB": "output/moduleB.css"
}

var cssHashWithCommonsChunkPlugin = {
   "common": "output/common.css" // <- moduleA and moduleB have no entries now :(
}

Is there a recommended approach to solving this? The only way I can think of doing it is to inject an entry into the CSS hash for src/moduleA and src/moduleB, which points to the bundled common.css. This will however result in multiple tags being injected that point to the same stylesheet which obviously isn't ideal.

Questions about flushing and babel plugin

Question 1

Is the babel plugin required? Or is it only helping for import() statements with calculated values?

Question 2

When I do SSR, I do 2 render passes: the first one to request data, and the second one after all data was finalized.
Only the components that were used in the second render pass should be preloaded on the client

However, I don't see a way to reset the cached list of required chunks. How does this work?

Question 3

React support streaming rendering, but that means that multiple renders might be running at the same time. How does the plugin know what chunks are required in what tree?

Basically, I am surprised there's no <ChunkProvider> around the React tree that is used to mark the used imports…

Unable to find an asset in Webpack chunks

Hola @faceyspacey,
first I want to congratulate you for nailing the code-splitting + SSR problem. Great work, my kudos to you!

However I'm facing the following issue: My imported universal component is not flushed out and so not included in SSR. I end up seeing a flash of "Loading..." when I render my app and get the checksum mismatch warning from react.

Looks to me like the assetsByChunkName are not in the order they should be?

This is how I define and render my splitted universal component:

import React from 'react'
import universal from 'react-universal-component'

const UniversalBlock = universal(props =>
  import(`./buildingBlocks/${props.type}`)
)

class Builder extends React.Component {
  render() {
    return <div>
      <UniversalBlock type="Image.js"/>
    </div>
  }
}

and this is how I flush the chunks in my server-side renderer

const { js, styles } = flushChunks(clientStats, {
  chunkNames: flushChunkNames(),
  before: ['bootstrap'],
  after: ['client']
})

then the output of js.toString() is:

<script type='text/javascript' src='http://localhost:8080/bootstrap.js'></script>
<script type='text/javascript' src='http://localhost:8080/client.js'></script>

and webpack-flush-chunks complains:

[FLUSH CHUNKS]: Unable to find buildingBlocks/Image.js in Webpack chunks. Please check usage of Babel plugin.

this the partial output of clientStats:

...
assetsByChunkName: {
  "buildingBlocks/RichText": [
    "buildingBlocks/RichText.js",
    "buildingBlocks/RichText.js.map"
  ],
  "buildingBlocks/Image": [
    "buildingBlocks/Image.js",
    "buildingBlocks/Image.js.map"
  ],
  "client": [
    "client.js",
    "client.js.map"
  ],
  "bootstrap": [
    "bootstrap.js",
    "bootstrap.js.map"
  ]
}
...

the babel plugin is included in my package.json:

...
"babel": {
  "presets": [
    "es2015",
    "es2017",
    "stage-3",
    "react"
  ],
  "plugins": [
    "transform-class-properties",
    "syntax-dynamic-import",
    "universal-import"
  ]
}
...

Webpack config file

const path = require('path')
const webpack = require('webpack')

module.exports = {
  name: 'client',
  target: 'web',
  devtool: 'source-map',
  entry: {
    client: path.resolve(__dirname, './src/client.js')
  },
  output: {
    filename: '[name].js',
    chunkFilename: '[name].js',
    path: path.resolve(__dirname, './dist'),
    publicPath: 'http://localhost:8080/'
  },
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      }
    ]
  },
  plugins: [
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: JSON.stringify(process.env.NODE_ENV)
      }
    }),
    new webpack.NamedModulesPlugin(),
    new webpack.optimize.CommonsChunkPlugin({
      names: ['bootstrap'], // needed to put webpack bootstrap code before chunks
      filename: '[name].js',
      minChunks: Infinity
    }),
    new webpack.HotModuleReplacementPlugin()
  ],
  devServer: {
    hotOnly: true,
    headers: { 'Access-Control-Allow-Origin': '*' },
    stats: { colors: true }
  }
}

get rid of [UNIVERSAL-IMPORT] no chunk, {file} , found in "window.__CSS_CHUNKS__". If you are not serving CSS for this chunk, disregard this message.

Is there a way to get rid of
[UNIVERSAL-IMPORT] no chunk, {file} , found in "window.__CSS_CHUNKS__". If you are not serving CSS for this chunk, disregard this message.

I use emotion (7.3.2) for dealing with css, so I usually write things like:

import React from 'react';
import styled from 'react-emotion';

const Container = styled('div')`
    width: 800px;
    margin: 30px auto 0;
`;

const Content = styled('div')`
    margin-top: 70px;
    margin-left: 10px;
    font-size: 20px;
    justify-content: center; 
`;

const Home = props =>
    (<Container>
        <Content>
            <p>text</p>
        </Content>
    </Container>);

export default Home;

So I'm not doing things like import './index.css' at the top of the file. Consequently, window.__CSS_CHUNKS__ does not have a css file retaled to its chunk.
I know I get only a warning, but it is a little annoying.
Can't we just disable it? Or provide an option for that?

Removing LimitChunkCountPlugin: 1 for server

I have fairly big single page application using react-universal-component and as it went big I had an issue when loading server code. The app was always running out of memory. The fix I used was to increase max-old-space-size when starting node, but I was thinking if it would not be possible to split server code into multiple files. This would make it much easier to handle for servers with limited memory and there would not be any need to increase memory.

Publish to NPM

It'd be great to have the NPM package updated (last one was 2 months ago). Great job, everyone!

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    πŸ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. πŸ“ŠπŸ“ˆπŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❀️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.