How code-splitting and dynamic Import works

Glad you asked. The documentation at webpack output.publicPath says this is one free variable __webpack_public_path__ which can be used to define the publicPath where webpack would look to lazy-load (or on-demand load) chunks. Concept of publicPath When creating an SPA (Single Page Application), all the assets usually resides at the root dist directory. So given the following webpack.config.js file If we host the files statically and serve directly, then bundle.js would be accessible from http://example.com/dist/bundle.js. Relying on this theory, we have set output.publicPath to be /dist/. So if './src/main.js' has some sort of import('...').then(({default}) => {}), webpack will look for the file http://example.com/dist/chunkName.js to load the dynamic import. This theory works really good, when we are in control of our HTML assets and URL. For example, we could very much put all the files inside dist to a CDN, and change publicPath to https://cdn.example.com/path/to/asset/dir. Usage inside WordPress The problem But when using WordPress, the static files of themes and plugins are located inside wp-content/themes and wp-content/plugins directory. The URL of these directories can be, pretty much anything. Say, you have WordPress installed in a sub-directory, so the URL would be https://example.com/path/to/wp/wp-content/plugins/my-plugin/dist. There is no way, we can know what the URL would be, during build-time. The Solution Luckily, from WordPress itself, we can very easily get the public URL of our asset (dist) directory using functions like plugins_url or get_stylesheet_directory_uri. This is the concept we use at wpackio. So it boils down to: 1: Generate publicPath with PHP library With the PHP script, we generate the publicPath URL and put it in website head within a <script> tag. Where __wpackIoAppNameOutputPath is a unique variable generated through a combination of your appName and outputPath from wpackio.project.js file. 2: Modify JavaScript entry-point Now a special entry-point is injected to all the entry members by wpackio. Say you have (in your wpackio.project.js) something like this: It gets converted into Notice the extra '@wpackio/entrypoint/lib/index.js'. This is a special module which has the following code Notice that we already have window.__wpackIoAppNameOutputPath set through PHP script. But this runtime script has no knowledge of the runtime variable __WPACKIO__. This is where webpack definePlugin comes in handy. Passing needed variable with webpack.DefinePlugin Through our webpack config we have This is how we pass in the needed appName and outputPath to our javascript files. Why in production build Now you may wonder why we couldn’t use the same technique for development server? The reason is: We would like to have webpack-dev-middleware serve files from memory, instead of files. This gives greater speed and doesn’t pollute your disk during HMR. For the above to work, we need to tell webpack-dev-middleware which URL the files should be served from. Hence, we assume our local dev server is always running from a domain root and the files are served from /wp-content/plugins/my-plugin/dist. So if you have some plugin to change the path to wp-content or the directory slug of themes and/or plugins within your development server, wpackio will surely not work. But given very rare chance of having such modification in development server, we have settled with the compromise. Do note that the limitation is valid only for development server. In production build, the publicPath is generated dynamically which will always work. Even so, if you are using @wpackio/scripts v2.8.0 or greater, you have another option distPublicPath which you can mention in your server configuration. This is useful if your WordPress development server gives an URL to the outputPath directory which is not the standard output or if your WordPress isn’t installed at the root domain (like http://localhost), rather a directory (like http://localhost/proj1).

Read More

How everything works together

This is an attempt to cover the working of this tooling. While this is really just a webpack and browser-sync based tooling, there are a few cases we had to consider to make sure it works nicely with WordPress. Choice of tooling We use webpack as the primary bundler. Browser Sync, along with webpack-dev-middleware acts as the development server. webpack-hot-middleware is used to provide all the HOT MODULE REPLACEMENT (HMR) goodness. babel-loader as the webpack module loader which uses babel under the hood to compile modern JavaScript/Typescript code. sass-loader, postcss-loader, autoprefixer along with mini-css-extract-plugin to handle CSS/SASS/SCSS source. Everything fits in together when we pass the middlewares to browsersync. But there are a few things we have to keep in mind so that webpack and WordPress play good together. Multiple webpack compiled assets It is possible to have multiple webpack compiled assets. So output.jsonpFunction needs to be unique. Runtime publicPath (dynamic) Unlike standalone applications, we do not have definite control of where our assets are served from. It resides inside plugin or theme directory. So output.publicPath has to be set during runtime. We also need a way to tell webpack in a conflict free way about this publicPath. During development In development server, we shouldn’t set publicPath dynamically, rather have it set to the plugin or theme output directory, considering WordPress is installed at root. This is needed to make webpack-hot-middleware work. In case if the URL to the outputPath directory is not something like http://host.tld/wp-content/plugins/<slug>/<outputPath>/, then we have an option distPublicPath which we can mention under wpackio.server.js file to define the URL path. For example, if your WordPress dev server is serving from http://localhost/wp-one, then you can pass /wp-one/wp-content/plugins/<slug>/<outputPath>/. There could be multiple webpack compiler (depending on wpackio.project.js), but to keep things fast, we are passing only one instance of webpack-dev and webpack-hot middleware. So we need to make sure: the listen path of webpack-hot-middleware is same across all webpack instances. the publicPath of webpack-dev-middleware is same across all webpack instances. We achieve this by having only one dist or outputPath in our config and pointing publicPath towards it. Then in the actual output.filename of webpack, we use sub-directory separator, like This gives us conflict free configuration for any number of webpack compiler. During production During production, we don’t make any assumptions at all. The reason is the same, we can not know if WordPress is installed in root or not. Whether wp-content, plugins and/or themes directory has been changed or not. So what we do instead is, we provide webpack runtime dynamic pubicPath by setting __webpack_public_path__ variable. If you want to know more about how publicPath works together with the PHP library read this concept note. Single runtime for different entrypoints While different webpack compiler instances are separated by different output.jsonpFunction there’s still one thing we need to consider. There should be only one runtime for every chunks. If we put multiple runtimes then the execution will be slower and there could be some race condition, overriding our dynamic imports. This is handled by telling webpack to split the runtime through a common chunk and then enqueueing it only once, using our PHP library. Enqueue common chunks only once Say we have the following configuration for files. Here it is very much possible, depending on our dependency graph that both main and mobile ends up using similar libraries. With the optimization in effect, webpack will create a common chunk for it, say main~mobile~vendor.js. Now if we were to enqueue both main and mobile in the same page, then we should take care that main~mobile~vendor.js is enqueued exactly once. This is again handled by Telling webpack to optimize using SplitChunksPlugin. Generating unique wp_enqueue $handle through our PHP library. In the background it just works, conflict free, thanks to the approach. Generate webpack config Keeping all these things in mind, the whole webpack configuration is created on the fly. I have tried to provide the best possible sane defaults, so that you don’t need to fiddle with the config. But if you want to, then it is only a away. But be extremely careful when doing this. If you fiddle with config.entry and config.output, you will probably break stuff. In most of the cases, you will never need to change those.

Read More