webpack 4: migration guide for plugins/loaders
This guide targets plugin and loader authors
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.
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 Map
s 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 Module
s. 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
NormalModule
s 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 Map
s instead of Object
s used as map.
Have fun.
Tobias