@morrain
Chrome & V8
Node
Npm
It turns out to be very fast, and it’s a very simple code base
It maks browser much more responsive、faster、stable and secure
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
“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!
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
Do not read documents in Chinese !
Do not read documents in Chinese !
Do not read documents in Chinese !
Concepts
Guides
Configuration
Loaders
Plugins
Api
Migrate
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
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) {
/***/ })
/******/ ]);
// 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>
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_require__更接近CommonJS的实现
对于ESModule规范的模块,会特殊处理,通过Object.defineProperty加上相应的__esModule的属性标识
import&export虽然是ESModule的规范,但却不需要Babel转码,可以webpack下开箱即用
Usage: entry: string|Array<string>
module.exports = {
entry: './path/xxx/file.js'
};
module.exports = {
entry: {
main: './path/xxx/file.js'
}
};
Usage: entry: {[entryChunkName: string]: string|Array<string>}
module.exports = {
entry: {
app: './src/app.js',
adminApp: './src/adminApp.js'
}
};
// 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'
]
}
]
}
};
install & config babel-loader
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')]
}
]
}
};
@babel/core AST转换的核心
@babel/cli 打包工具
@babel/plugin* 插件机制,Babel基础功能不满足的时候,手动添加些
@babel/preset-env 把许多 @babel/plugin*组合一起
@babel/polyfill 把浏览器某些不支持的api,导入到项目中,可以全部导入,也可以按需导入
@babel/runtime 解决polyfill直接修改api带来的因模块复用导致冲突的问题
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
};
}
// 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
});
// 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
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';
// 安装@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
};
}
The first secret of success
Is there a sequence between these plugins?
// src/index.js
console.log('hello webpack')
node ./src/index.js
node --inspect-brk ./src/index.js
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"
}
The second secret of success
// 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而言,通过它来完成数据的处理
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;
// 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;
The second secret of success
The third secret of success