how to learn webpack?

@morrain
  • Chrome & V8
  • Node
  • Npm

About FrontEnd

Webkit

It turns out to be very fast, and it’s a very simple code base 

MultiProcess

It maks browser much more responsive、faster、stable and  secure

V8

It is a major technological breakthrough in Chrome.
“More importantly, most web developers don’t use JavaScript a lot because it doesn’t run that fast in a browser.”

                      —— Sundar Pichai

 

chorme 发布时漫画
“So with V8, we hope it will not only run chrome faster, but it will enable a whole new class of applications for tomorrow.”

                      —— Sundar Pichai

 

+

@2009
@2010
When Node.js was released a new era started!

Why webpack?

  • old ways to run js in a browser  leads to problemsin scope, size, readability and maintainability
  • no browser support for CommonJS
  • browser support for ESM is incomplete

It is ! But far more than that!

Do not read documents in Chinese !

Do not read documents in Chinese !

Do not read documents in Chinese !

Overview of webpack documents

  • Concepts
  • Guides
  • Configuration
  • Loaders
  • Plugins
  • Api
  • Migrate

install

npm init
// package.json  
"scripts": {
    "start": "webpack"
}

npm i -D webpack
npm i -D webpack-cli


//新建  src/index.js


//webpack4是开箱即用的,刚开始不需要做任何配置,
//默认从src/index.js寻找入口,输出到dist/main.js文件。
npm run start

webpack runtime

The runtime is basically all the code webpack needs to manage those modules while it's running in the browser

It contains the loading、resolving and cache logic

 

It adapts import&require with it's  '__webpack_require__'

/******/ (function(modules) { // webpackBootstrap
/******/ 	// The module cache
/******/ 	var installedModules = {};
/******/
/******/ 	// The require function
/******/ 	function __webpack_require__(moduleId) {
/******/
/******/ 		// Check if module is in cache
/******/ 		if(installedModules[moduleId]) {
/******/ 			return installedModules[moduleId].exports;
/******/ 		}
/******/ 		// Create a new module (and put it into the cache)
/******/ 		var module = installedModules[moduleId] = {
/******/ 			i: moduleId,
/******/ 			l: false,
/******/ 			exports: {}
/******/ 		};
/******/
/******/ 		// Execute the module function
/******/ 		modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ 		// Flag the module as loaded
/******/ 		module.l = true;
/******/
/******/ 		// Return the exports of the module
/******/ 		return module.exports;
/******/ 	}
/******/
/******/
/******/ 	// expose the modules object (__webpack_modules__)
/******/ 	__webpack_require__.m = modules;
/******/
/******/ 	// expose the module cache
/******/ 	__webpack_require__.c = installedModules;
/******/
/******/ 	// define getter function for harmony exports
/******/ 	__webpack_require__.d = function(exports, name, getter) {
/******/ 		if(!__webpack_require__.o(exports, name)) {
/******/ 			Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ 		}
/******/ 	};
/******/
/******/ 	// define __esModule on exports
/******/ 	__webpack_require__.r = function(exports) {
/******/ 		if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ 			Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ 		}
/******/ 		Object.defineProperty(exports, '__esModule', { value: true });
/******/ 	};
/******/
/******/ 	// create a fake namespace object
/******/ 	// mode & 1: value is a module id, require it
/******/ 	// mode & 2: merge all properties of value into the ns
/******/ 	// mode & 4: return value when already ns object
/******/ 	// mode & 8|1: behave like require
/******/ 	__webpack_require__.t = function(value, mode) {
/******/ 		if(mode & 1) value = __webpack_require__(value);
/******/ 		if(mode & 8) return value;
/******/ 		if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ 		var ns = Object.create(null);
/******/ 		__webpack_require__.r(ns);
/******/ 		Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ 		if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ 		return ns;
/******/ 	};
/******/
/******/ 	// getDefaultExport function for compatibility with non-harmony modules
/******/ 	__webpack_require__.n = function(module) {
/******/ 		var getter = module && module.__esModule ?
/******/ 			function getDefault() { return module['default']; } :
/******/ 			function getModuleExports() { return module; };
/******/ 		__webpack_require__.d(getter, 'a', getter);
/******/ 		return getter;
/******/ 	};
/******/
/******/ 	// Object.prototype.hasOwnProperty.call
/******/ 	__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ 	// __webpack_public_path__
/******/ 	__webpack_require__.p = "";
/******/
/******/
/******/ 	// Load entry module and return exports
/******/ 	return __webpack_require__(__webpack_require__.s = 0);
/******/ })
/************************************************************************/
/******/ ([
/* 0 */
/***/ (function(module, exports) {



/***/ })
/******/ ]);

entry&output

// webpack.config.js
const path = require('path');
module.exports = {
    entry: './src/index.js',
    output: {
        filename: 'bundle.js',
        path: path.resolve(__dirname, './dist')
    },
    optimization: {
        minimizer: []//webpack4是开箱即用的,会默认压缩代码。此处此时这么配置是为了去掉默认的压缩
    }
};


// package.json
"scripts": {
    "start": "webpack --config webpack.config.js"
}


// src/index.js
console.log('hello webpack!')


// index.html
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <title>hello webpack</title>
    </head>
    <body>
        <script src="./dist/bundle.js"></script>
    </body>
</html>

Debug it!

Debug it!

Debug it!

The first secret of success

// src/m.js   新建m.js,并使用commonjs导出
exports.log = function (info) {
    console.log(info);
}
// src/index.js   index.js中使用commonjs导入
var log = require('./m').log;
log('load m module');
import: CommonJS
export: CommonJS
// src/m.js
export function log(info) {
    console.log(info);
}
// src/index.js   index.js中使用commonjs导入
var log = require('./m').log;
log('load m module');
import: CommonJS
export: ESM
 // src/m.js
 exports.log = function (info) {
     console.log(info);
 } 
 // src/index.js
 import { log } from './m';
 log('load m module');
import: ESM
export: CommonJS
 // src/m.js
 export function log(info) {
    console.log(info);
 }
 // src/index.js
 import { log } from './m';
 log('load m module');
import: ESM
export: ESM
  • 不管用CommonJS的require&exports 还是ESModule的import&export,webpack都是转化为__webpack_require__方法来加载的

调试完webpack的runtime代码后,基本能得出如下结论:

  • __webpack_require__更接近CommonJS的实现
  • 对于ESModule规范的模块,会特殊处理,通过Object.defineProperty加上相应的__esModule的属性标识

  • import&export虽然是ESModule的规范,但却不需要Babel转码,可以webpack下开箱即用

Single Entry (Shorthand) Syntax

Usage: entry: string|Array<string>
module.exports = {
  entry: './path/xxx/file.js'
};
module.exports = {
  entry: {
    main: './path/xxx/file.js'
  }
};

Object Syntax

Usage: entry: {[entryChunkName: string]: string|Array<string>}
module.exports = {
  entry: {
    app: './src/app.js',
    adminApp: './src/adminApp.js'
  }
};

loader

// config demo
module.exports = {
    ...
    module: {
        rules: [
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [path.resolve(__dirname, '../src')]
            },
            {
                test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
                loader: 'url-loader',
                options: {
                    limit: 10000,
                    name: path.posix.join('static', 'img/[name].[hash:7].[ext]')
                }
            },
            {
                test: /\.sass$/,
                use: [
                    'style-loader',
                    {
                        loader: 'css-loader',
                        options: {
                            sourceMap: true
                        }
                    },
                    'sass-loader'
                ]
            }
        ]
    }
};

babel-loader

  1. install & config babel-loader
  2. install & config babel
// src/testBabel.js    新建测试文件
setTimeout(() => {
    let a = 'morrain';
    let b = 'morrain2';
    console.log(`timer done! ${a} do it`);
    console.log(a + b);
}, 1000);

let array = [1, 2, 3, 4, 5, 6];
array.includes(item => item > 2);

let tpx = new Promise((resolve, reject) => {
    console.log('new Promise done');
});

Object.assign({}, {
    a: 1,
    b: 2
});

// src/index.js
console.log('hello webpack')
import './testBabel';

npm run start
npm i -D babel-loader    //安装babel-loader


// webpack.config.js
module.exports = {
    ……
    module: {
        rules: [//增加对js文件使用babel-loader进行解析的配置
            {
                test: /\.js$/,
                loader: 'babel-loader',
                include: [path.resolve(__dirname, './src')]
            }
        ]
    }
};

about Babel

about Babel

  • @babel/core AST转换的核心
  • @babel/cli 打包工具
  • @babel/plugin* 插件机制,Babel基础功能不满足的时候,手动添加些
  • @babel/preset-env 把许多 @babel/plugin*组合一起
  • @babel/polyfill 把浏览器某些不支持的api,导入到项目中,可以全部导入,也可以按需导入
  • @babel/runtime 解决polyfill直接修改api带来的因模块复用导致冲突的问题

Babel usage

npm i -D @babel/core @babel/preset-env


// babel.config.js
module.exports = function (api) {
    api.cache(true);

    const presets = [
        [
            "@babel/env",
            {
                debug: true
            }
        ]
    ];
    const plugins = [];

    return {
        presets,
        plugins
    };
}


compare changes

// src/testBable.js
setTimeout(() => {
    let a = 'morrain';
    let b = 'morrain2';
    console.log(`timer done! ${a} do it`);
    console.log(a + b);
}, 1000);

let array = [1, 2, 3, 4, 5, 6];
array.includes(item => item > 2);

let tpx = new Promise((resolve, reject) => {
    console.log('new Promise done');
});

Object.assign({}, {
    a: 1,
    b: 2
});


// dist/bundle.js
setTimeout(function () {
  var a = 'morrain';
  var b = 'morrain2';
  console.log("timer done! ".concat(a, " do it"));
  console.log(a + b);
}, 1000);
var array = [1, 2, 3, 4, 5, 6];
array.includes(function (item) {
  return item > 2;
});
var tpx = new Promise(function (resolve, reject) {
  console.log('new Promise done');
});
Object.assign({}, {
  a: 1,
  b: 2
});


about polyfill

  // polyfill demo

  if (typeof Object.assign != 'function') {
      Object.defineProperty(Object, "assign", 
      ·····
  }
  if (!Array.prototype.includes){
     Object.defineProperty(Array.prototype, 'includes',
      ·····
  }
  if (!Array.prototype.every){
     Object.defineProperty(Array.prototype, 'every',
      ·····
  }

polyfill usage

// 安装polyfill
npm i -s @babel/polyfill //注意是-s不是-D

// babel.config.js
module.exports = function (api) {
    api.cache(true);

    const presets = [
        [
            "@babel/env",
            {
                debug: true,
                useBuiltIns: 'entry'//新加的参数
            }
        ]
    ];
    const plugins = [];

    return {
        presets,
        plugins
    };
}

// src/index.js
import "@babel/polyfill";//新加的导入

import './testBabel';

replace polyfill with runtime

// 安装@babel/plugin-transform-runtime到devDependencies
npm i -D @babel/plugin-transform-runtime  
// 安装@babel/runtime-corejs2 到 dependencies
npm i -s @babel/runtime-corejs2  
// 去掉@babel/polyfill    
npm uninstall @babel/polyfill 

// babel.config.js
module.exports = function (api) {
    api.cache(true);

    const presets = [
        [
            "@babel/env",
            {
                debug: true//去掉了useBuiltIns参数,不再使用polyfill
            }
        ]
    ];
    const plugins = [
        [
            "@babel/plugin-transform-runtime",//新增加babel-runtime插件
            {
                "corejs": 2
            }
        ]
    ];

    return {
        presets,
        plugins
    };
}

Debug it!

Debug it!

Debug it!

The first secret of success

plugin

Is there a sequence between these plugins?

debug node

// src/index.js
console.log('hello webpack')


node ./src/index.js


node --inspect-brk ./src/index.js

two ways to debug node

debug webpack

npm run start

"scripts": {
    "start": "webpack --config webpack.config.js"
}

// 与上面的写法是等价的,请参考《通过npm包来制作命令行工具的原理》
"scripts": {
    "start": "node ./node_modules/webpack/bin/webpack.js --config webpack.config.js"
}


"scripts": {
    "start": "node --inspect-brk ./node_modules/webpack/bin/webpack.js --config webpack.config.js"
}

Debug webpack!

Debug webpack!

Debug webpack!

The second secret of success

write a plugin to understand the plugin

// build/CustomPlugin.js
class HelloWebpackPlugin {
    constructor(params) {
        console.log('HelloWebpackPlugin init: params=', params)
    }
    apply(compiler) {
        //complier.hooks里有所有complier的钩子,这里注册了done的事件,表示当webpack构建完成时触发
        compiler.hooks.done.tap('HelloWebpackPlugin', stats => {
            console.log('HelloWebpackPlugin: webpack done!');
        });
    }
}
module.exports = HelloWebpackPlugin;


// webpack.config.js
const HelloWebpackPlugin = require('./build/CustomPlugin');
module.exports = {
    ...
    plugins: [
        new HelloWebpackPlugin()
    ]
};
compiler 是plugin的apply接口传进来的参数,它代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin等。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用,可以使用它来访问 webpack 的主环境。对于 plugin 而言,通过它来注册事件钩子
compilation 对象代表了一次资源版本构建。当 webpack 开启watch时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。对于plugin而言,通过它来完成数据的处理
compiler 是plugin的apply接口传进来的参数,它代表了完整的 webpack 环境配置。这个对象在启动 webpack 时被一次性建立,并配置好所有可操作的设置,包括 options,loader 和 plugin等。当在 webpack 环境中应用一个插件时,插件将收到此 compiler 对象的引用,可以使用它来访问 webpack 的主环境。对于 plugin 而言,通过它来注册事件钩子
compilation 对象代表了一次资源版本构建。当 webpack 开启watch时,每当检测到一个文件变化,就会创建一个新的 compilation,从而生成一组新的编译资源。一个 compilation 对象表现了当前的模块资源、编译生成资源、变化的文件、以及被跟踪依赖的状态信息。对于plugin而言,通过它来完成数据的处理

what is Tapable?

  • tap

  • tapAsync

  • tapPromise

// build/CustomPlugin.js
class HelloWebpackPlugin {
    constructor(params) {
        console.log('HelloWebpackPlugin init: params=', params)
    }
    apply(compiler) {

        const plugin_name = 'HelloWebpackPlugin';
        //complier.hooks里有所有complier的钩子,这里注册了done的事件,表示当webpack构建完成时触发
        compiler.hooks.done.tap(plugin_name, stats => {
            console.log('HelloWebpackPlugin: webpack done! ');
        });

        compiler.hooks.emit.tapAsync(plugin_name, (compilation, callback) => {
            // 做一些异步的事情……
            setTimeout(function () {
                console.log('Done with async work...');
                callback();
            }, 1000);
        });

        compiler.hooks.emit.tapPromise(plugin_name, compilation => {
            // 返回一个 Promise,在我们的异步任务完成时 resolve……
            return new Promise(resolve => {
                setTimeout(function () {
                    console.log('异步工作完成……');
                    resolve();
                }, 1000);
            });
        });
    }
}

module.exports = HelloWebpackPlugin;

Is there a sequence between these plugins?

the full life-cycle of webpack

// webpack.config.js
const CompilerHooks = require('./build/CompilerHooks');
module.exports = {
    ……
    plugins: [
        new CompilerHooks()
    ]
};

// build/CompilerHooks.js
class CompilerHooks {
    constructor() {}
    apply(compiler) {

        const plugin_name = 'CompilerHooks';
        const hooks = compiler.hooks;

        hooks.entryOption.tap(plugin_name, (context, entry) => {
            console.log(`${plugin_name}:entryOption fired!`);
        });

        hooks.afterPlugins.tap(plugin_name, (compiler) => {
            console.log(`${plugin_name}:afterPlugins fired!`);
        });

        hooks.run.tapPromise(plugin_name, compiler => {
            console.log(`${plugin_name}:run fired!`);

            return new Promise(resolve => {

                setTimeout(() => {
                    resolve()
                }, 1000);

            });
        });

        hooks.compile.tap(plugin_name, compilationParams => {
            console.log(`${plugin_name}:compile fired!`);
        });
        hooks.compilation.tap(plugin_name, (compilation, compilationParams) => {
            console.log(`${plugin_name}:compilation fired!`);

            const hooks = compilation.hooks;

            hooks.buildModule.tap(plugin_name, module => {
                console.log(`${plugin_name}:compilation:buildModule fired!`);
            })

            hooks.optimizeChunks.tap(plugin_name, chunks => {
                console.log(`${plugin_name}:compilation:optimizeChunks fired!`);
            })

            hooks.succeedModule.tap(plugin_name, module => {
                console.log(`${plugin_name}:compilation:succeedModule fired!`);
            })

            hooks.finishModules.tap(plugin_name, module => {
                console.log(`${plugin_name}:compilation:finishModules fired!`);
            })

        });

        hooks.make.tapPromise(plugin_name, compilation => {
            console.log(`${plugin_name}:make fired!`);

            return new Promise(resolve => {

                setTimeout(() => {
                    resolve()
                }, 1000);

            });
        });

        hooks.afterCompile.tapPromise(plugin_name, compilation => {
            console.log(`${plugin_name}:afterCompile fired!`);

            return new Promise(resolve => {

                setTimeout(() => {
                    resolve()
                }, 1000);

            });
        });


        hooks.emit.tapPromise(plugin_name, compilation => {
            console.log(`${plugin_name}:emit fired!`);

            return new Promise(resolve => {

                setTimeout(() => {
                    resolve()
                }, 1000);

            });
        });

        hooks.done.tapPromise(plugin_name, stats => {
            console.log(`${plugin_name}:done fired!`);

            return new Promise(resolve => {

                setTimeout(() => {
                    resolve()
                }, 1000);

            });
        });
    }
}

module.exports = CompilerHooks;

Debug webpack!

Debug webpack!

Debug webpack!

The second secret of success

How to use webpack for long-term caching

put into practice!

put into practice!

put into practice!

The third secret of success

Q&A

webpack learning

By morrain

webpack learning

how to learn webpack

  • 442