Webpack 4 is the standard at the time of this course.
It is a backbone of the webpack of the plugin system.
Things like the Compiler
extend Tapable
. They have hooks now that are static to see what you can plug into.
// This is only v3 shown for demo purposes. // These days you can plug into via the hooks. class BasicPlugin { constructor() {} apply(compiler) { compiler.plugin('make', compilation => { console.log('I have access to the compilation!'); }); } } module.exports = BasicPlugin;
To access any Tapable instances, you need to go through the compiler.
There are 7-ish Tapable instances in this talk that Sean deems as the most important.
The compiler:
The meat and potatoes of the Webpack. It is the dependency graph.
"Finds out if it exists."
Factories create instances/objects. The module factories does just that.
Parser creates the ASTs and begins walking the graph.
Webpack uses Acorn by default.
Data binding for the modules. Creates the source code you see in your bundles.
There are hundreds of properties exposed out of the box.
webpack.options.apply
takes the config and "basically runs a huge switch statement" and changes the module based on the env ie Electron, etc.
It is a class with an
apply
method.
// plugin class MyFirstWebpackPlugin { // this is the important method apply(compiler) { compiler.hooks.done.tapAsync("MyFirstWebpackPlugin", (stats, cb) => { console.log(stats); cb; })); } } module.exports = MyFirstWebpackPlugin; // in webpack.config.js const MyFirstWebpackPlugin = require('path/to/MyFirstWebpackPlugin'); // ... later in the code plugins: [new MyFirstWebpackPlugin()];
Use
webpack/lib/Compiler.js
to see all the possible hooks!
The important part of any hook is the tapAsync
method:
compiler.hooks.done.tapAsync("MyFirstWebpackPlugin", (stats, cb) => { console.log(stats); debugger; // a cool tip on debugging the webpack plugin at chrome://inspect cb(); }));
Using the debugger can tell you so much about why things get tree shaken etc. You can see it in verbose in the debug console using stats.toString("verbose")
.
A useful hook to see the outputs:
compiler.hooks.done.tapAsync("MyFirstWebpackPlugin", (stats, cb) => { const assetNames = []; for (let assetName in stats.compilation.assets) { assetName.push(assetName) } console.log(assetNames.join("\n")) cb(); }));
For hooking into another instance, we can use another hook:
compiler.hooks.compilation.tapAsync("MyFirstWebpackPlugin", (compilation, params) => { const thisCompilationIWantToInspect = compilation; compilation.hooks.seal.tap("MyFirstWebpackPlugin", () => { // annoying gotcha - need to use the variable for it to show in VSCode inspector console.log(thisCompilationIWantToInspect) debugger; // we can now inspect the above }) }));
Everything is in memory, so there is no concept of importing fs
from Node. There is a file MemoryOutputFileSystem.js
that has a like of requiring memory fs.
Webpack uses an in-memory file system, simile to memfs
Something else that is cool is that you can take another plugin and use that within your plugin! You can bring your own plugins into other plugins.
// webpack.config.js module.exports = () => ({ resolveLoader: { alias: { 'my-loader': require.resolve('./build-utils/my-loader.js'), }, }, module: { reults: [ { test: /\.js/, use: 'my-loader', }, ], }, }); // my-loader.js function myLoader(source) { debugger; // example Sean uses if (this.resource === 'path/to/file') { // do something with resource } return source; } module.exports = myLoader;
Something that is great, is that if you step over while the bugger is on, you'll got through all js
files!
In the example, Sean just uses a simmple.
It is important not to transpile Webpack down to CommonJS - this is a common pitfall and will cause issues for scope hoisting, tree shaking etc.
The is an option to use "modules": false
in the Babel config to ensure Webpack isn't down-converted.