webpack 4: migration guide for plugins/loaders

This guide targets plugin and loader authors

Tobias Koppers
webpack
Published in
5 min readJan 29, 2018

--

For loaders:

this.options and this.rootContext

webpack 3 already deprecated this.options in the loader context. webpack 4 removes it now.

A lot of people are missing the this.options.context value now. It has been added as this.rootContext.

Guideline: Loaders should receive all options via this.query. They should not use other ways to receive options, i. e. no property in webpack options, no environment variable.

HMR code

this.hot was added to the loader context. Loaders can use it to generate HMR specific code.

AST passing

It’s now also possible to pass AST directly from loader to webpack to avoid double parsing.

webpack will happily accept any AST you pass. If it doesn’t match source code, bad things will happen.

We made a quick plugin migration video courtesy of https://webpack.academy

For plugins:

New Plugin system

webpack 4 uses a new plugin system, which is only partially compatible with the old one.

See documentation here: https://github.com/webpack/tapable

The plugin method on tapable objects is theoretically backward-compatible, but it causes a deprecation warning, so plugins should consider switching to tap instead.

Each function attached to a hook must now have a name. This name is used by i. e. the ProgressPlugin to display the plugin in the progress or the ProfilingPlugin to name the profiled timings for function.

There are 3 variants of tap : tap, tapAsync and tapPromise. Choose what fits best for you. Note that some hooks only allow the sync tap variant.

There are additional steps needed when defining custom hooks: You need to declare the hook now by adding a property to the hooks object.

See also: https://blog.johnnyreilly.com/2018/01/finding-webpack-4-use-map.html

enhanced-resolve major upgrade

enhanced-resolve was also upgraded to the new plugin system. This required a few interfaces changes too. The callback function no longer carries additional properties. Instead a resolverContext object is passed as additional argument.

Resolver plugins are backward-compatible, but should be migrated to the new plugin system.

Templates render manifest

Templates can now generate multiple assets. To do so a new method getRenderManifest returns an array of asset configurations with render functions and filename template.

This can be modified via plugin hook. Plugins can use this to generate assets from chunks.

Chunk graph and ChunkGroups

A major change happened with the chunk graph. In webpack 3 and lower chunks were connected with parent-child-relationships and contain modules. A chunk with multiple parents could only rely on the assumption that at least one parent is already loaded at runtime.

These schema is insufficient for advanced optimization, especially when aggressively splitting chunks into smaller pieces.

The new chunk graph in webpack 4 has chunk groups connected via parent-child-relationships which contain chunks which contain modules. All chunks of a chunk group are loaded in parallel and can rely on the fact that at least one parent chunk group is already loaded at runtime.

Chunk groups do not affect the output. Only chunks and modules are known at runtime. They are only used for optimization.

An AsyncDependenciesBlock (import()) now points to a single chunk group, instead of to a list of chunks as in webpack ≤ 3.

In addition to that change some mapXXX and forEachXXX helper methods in Module and Chunk were removed/deprecated in favor of using Array.from(xxxIterable).

Parser renames and definitions

The Parser used to track definitions and variable renames in scope via an Array and an Object used as map. To check existence in an Array indexOf was used. When entering a child scope these info has to be carried over but when leaving a child scope it should not leak out of the scope.

webpack 3 uses clever tricks to avoid copying the data: For Arrays length is reseted back to original value when leaving the scope. For Objects a prototype chain is used via Object.create.

Migrating this to ES15 and Map and Set was not trivial, so a new datastructure was introduced. It uses a multiple levels for Maps to avoid copying data while keeping fast access. It has a Map/Set-like interface.

Compiler

While applying plugins Compiler.options is no longer accessible. Plugins should not access options. They should only depend on options passed to the plugin via constructor.

The Compilation.notCacheable flag was removed

EcmaScript modules dependencies

The internal Dependencies for ESM support has changed fundamentally.

Module.buildMeta.harmony was removed. Module.buildMeta.exportsType was added. exportsType defines how the exports of a module are handled by an import statement (and import()).

exportsType = undefined handles them like CommonJS. The exports are exposed via default import. In non-strict esm mode named imports are mapped to properties on the exports at runtime. A truthy __esModule property maps the default import to the default property instead (non-strict esm mode).

exportsType = "namespace" handle them like ESM. Named imports are mapped to properties of the exports object. The default import is mapped to the default property.

exportsType = "named": Named imports are mapped to properties of the exports object. The default import is mapped directly to the exports object. (This is used for JSON)

When usedExports is an Array, properties are mangled.

Dependency

Dependency.getReference() may now return a weak property. Dependency.weak is now used by the Dependency base class and returned in the base impl of getReference().

Dependency.isEqualResource has been replaced with Dependency.getResourceIdentifier for performance reasons.

Module types

Constructor arguments changed for all Modules. Most of them use an options object now. A type argument was added to the Module constructor.

Per-module Resolvers

webpack now supports different Resolvers per Module. This means it’s no longer possible to change the “global” resolvers. Instead hooks can be used to modify resolvers when created. Compiler.resolvers is no longer available. Instead Compiler.resolverFactory has hooks to hook into resolver creation.

Template

The Template class used to be a base class of all Templates. It’s now a static class with helper methods.

RuntimeTemplate

A new RuntimeTemplate class has been added and outputOptions and requestShortener has been moved to this class.

Many methods has been updated to use the RuntimeTemplate instead. Some generated code has been move into the RuntimeTemplate. We plan to add hooks to the RuntimeTemplate to modify the generated code, but this has not happened yet (Won’t be a breaking change).

Module

Module.meta has been replaced with Module.buildMeta and Module.buildInfo. buildMeta carry all information which is required to import a module correctly. buildInfo carries internal information which is not required for importing, but may be required for generating the module’s source. The plan is to keep less properties directly on the Module class. Most properties has been moved into buildInfo or factoryMeta, which has also been added.

Generator

NormalModules now take a Generator instance as constructor argument. This instance handles the Code Generation of the Module. It was separate to allow customizing Code Generation for custom module types.

generator and createGenerator hooks has been added to the NormalModuleFactory to customize it.

Maps and Sets

Compiler.fileTimestamps and Compiler.contextTimestamps are now Maps instead of Objects used as map.

Have fun.

Tobias

--

--