概述
webpack 是一个现代 JavaScript 应用程序的静态模块打包器(module bundler)。当 webpack 处理应用程序时,它会递归地构建一个依赖关系图(dependency graph),其中包含应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。
首先我们创建一个文件夹,执行npm init 初始化工程后,执行
npm install webpack webpack-cli --save-dev
在工程根目录创建一个index.js
console.log('hello webpack')
npx 执行
npx是npm5.2版开始新增的命令,应用直接调用工程内部已经安装的模块
在此之前我们需要在package.json的scripts配置,然后执行npm run xx
执行
npx webpack --mode=development ./index.js
dist/main.js
执行完成了以后我们发现工程中多了一个dist/main.js,查看其代码发现一句console.log最终经过webpack打包生成了一段很长的代码
(function(modules) {
var installedModules = {};
function __webpack_require__(moduleId) {
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
__webpack_require__.m = modules;
__webpack_require__.c = installedModules;
__webpack_require__.d = function(exports, name, getter) {
if(!__webpack_require__.o(exports, name)) {
Object.defineProperty(exports, name, { enumerable: true, get: getter });
}
};
__webpack_require__.r = function(exports) {
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}
Object.defineProperty(exports, '__esModule', { value: true });
};
__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;
};
__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;
};
__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
__webpack_require__.p = "";
return __webpack_require__(__webpack_require__.s = "./index.js");
})
({
"./index.js":
(function(module, exports) {
eval("console.log('hello webpack')\n\n//# sourceURL=webpack:///./index.js?");
})
});
我们来看一下这段代码 首先它是一个立即执行函数,内部有一个 __webpack_require__ 方法
modules参数为模块对象,包含了所有的模块,key是模块的路径,value是模块的代码
(function(modules){
function __webpack_require__(){
}
})({})
__webpack_require__
(function(modules) {
//缓存对象,已经解析的模块存储在installedModules
var installedModules = {};
function __webpack_require__(moduleId) {
//判断当前的模块id是否在缓存中,如果在则直接返回代码
if(installedModules[moduleId]) {
return installedModules[moduleId].exports;
}
//创建当前的模块对象,写入缓存
var module = installedModules[moduleId] = {
i: moduleId,
l: false,
exports: {}
};
//执行当前模块代码
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
module.l = true;
return module.exports;
}
//启动程序
return __webpack_require__(__webpack_require__.s = "./index.js");
})
({
//modules对象
});
依赖
刚刚这个例子我们的index.js只是单纯的console语句,实际场景下我们的项目的js是相互依赖的,我们跑一个依赖的情况观察下__webpack_require__ 是如何执行多个文件的
在工程根目录创建一个item.js
export const item = ['A','B','C']
在工程根目录创建一个list.js
import {item} from './item'
export const list = [1,2,3]
修改index.js
import {list} from'./list.js'
console.log(list)
console.log('hello webpack')
执行 npx webpack --mode=development ./index.js 我们观察dist/main.js modules对象
({
"./index.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _list_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./list.js */ \"./list.js\");\n\nconsole.log(_list_js__WEBPACK_IMPORTED_MODULE_0__[\"list\"])\nconsole.log('hello webpack')\n\n//# sourceURL=webpack:///./index.js?");
}),
"./item.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"item\", function() { return item; });\nconst item = ['A','B','C']\n\n//# sourceURL=webpack:///./item.js?");
}),
"./list.js":
(function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"list\", function() { return list; });\n/* harmony import */ var _item__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./item */ \"./item.js\");\n\nconst list = [1,2,3]\n\n//# sourceURL=webpack:///./list.js?");
})
});
"./index.js"通过__webpack_require__ 调用了 "./list.js"
"./list.js"通过__webpack_require__ 调用了 "./item.js"
结合 __webpack_require__ 函数中的
modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
我们可以得出结论:webpack通过将模块放入一个对象中,key为文件路径,value是一个函数,内部是其代码,对象传入自执行函数内,内部通过__webpack_require__ 函数来递归解析
webpack.config.js
webpack推荐创建一个webpack.config.js的文件来定义配置
const path = require('path')
module.exports = {
//入口
entry: './index.js',
//出口
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
}
}
执行
npx webpack --mode=development
成功之后在dist文件夹下生成了一个index.bundle.js
loader
loader 用于对模块的源代码进行转换。loader 可以使你在 import 或"加载"模块时预处理文件。因此,loader 类似于其他构建工具中“任务(task)”,并提供了处理前端构建步骤的强大方法。loader 可以将文件从不同的语言(如 TypeScript)转换为 JavaScript,或将内联图像转换为 data URL。loader 甚至允许你直接在 JavaScript 模块中 import CSS文件!
举个栗子
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}
]
}
}
运行这个配置,webpack会调用babel-loader将 ECMAScript 2015+ 版本的代码转换为向后兼容的 JavaScript 语法,以便能够运行在当前和旧版本的浏览器或其他环境中。这样我们就可以在项目中使用ES6 了。那么这个babel-loader是什么?
我们写一个简单的loader
修改index.js
console.log('hello webpack')
工程根目录下新建loader1.js
const loaderUtils = require('loader-utils')
module.exports = function (source) {
console.log('>>>>>>loader1<<<<<<<<')
//通过loader-utils模块获取webpack中对应options
const optionsName = loaderUtils.getOptions(this).name || ''
return source.replace(/hello/g, optionsName)
}
工程根目录下新建loader2.js
module.exports = function (source) {
console.log('>>>>>>loader2<<<<<<<<')
return source.replace(/webpack/g, '世界')
}
修改webpack.config.js
const path = require('path')
module.exports = {
entry: './index.js',
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].bundle.js'
},
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
use: [{
loader: './loader1.js',
options: {
name: '您好'
}
}, './loader2.js'],
}
]
}
}
运行 npx webpack --mode=development
node控制台输出
>>>>>>loader2<<<<<<<<
>>>>>>loader1<<<<<<<<
浏览器控制台输出
您好 世界
由此可以看到
loader其实是一个函数,根据test的匹配规则,将.js的文件读取出来作为参数传递给函数loader2,函数loader2处理完成,将内容作为参数传递给函数loader1,函数loader1最终返回代码
webpack通过倒序迭代loader函数(每个loader是职责单一的函数),最终返回处理好的代码
插件
webpack 插件是一个具有 apply 属性的 JavaScript 对象。apply 属性会被 webpack compiler 调用,并且 compiler 对象可在整个编译生命周期访问。