webpack知识拓展
devtool
- 开发工具,默认为true,会将编译的代码使用eval包裹
- 可以设置为false,来关闭
mode
- 可以通过package命令设置mode,如
webpack --mode=development
- 可以通过process.env.NODE_ENV获取到
- 在config.js中通过这个拿不到,执行代码中可以拿到
- 但往往我们是需要判断某个值,执行某种打包方式,一些参数也依赖于某个变量,所以会通过设置环境变量的方式来实现
跨平台赋值
- 因为mac与window的设置环境变量方法不一样,为了保证协同开发,所以我们使用一个第三方库来设置
- 第三方库:cross-env
"build": "cross-env NODE_ENV=development webpack"
console.log(process.env.NODE_ENV);
plugins
自定义环境变量
- 如process.env.NODE_ENV这种变量,实际上是webpack在打包时根据key进行字符串替换
- 所以我们再编译后的代码中可以看到直接输出的是某个值
- 通常我们也需要自己定义一些变量,可以通过
webpack.DefinePlugin来管理一些key和value,在打包时由webpack进行查询并替换
- 例子:
plugins: [
new webpack.DefinePlugin({
diy: JSON.stringify("我是自定义的值"),
}),
],
console.log(diy);
console.log("我是自定义的值");
- 我们已经知道了环境变量是进行替换的,那么如果没有相对于的变量,则不会替换,那么代码就会报错,如
console.log(process.env.asda);
使用外置文件.env设置
- 有一些情况,我们的变量只想在conf文件中使用,并不希望打包像上述例子中打包进代码
- 第三方库dotenv
- 创建一个.env文件,在webpack.config.js中导入,然后执行
.config()方法即可,会默认将.env文件中的变量放入到环境变量process.env中
- 在任意文件中引入都可以,不过我们当前期望在conf文件使用,就在conf中导入
- 当然,我们也可以配合webpack.DefinePlugin来使用,毕竟好多变量写在命令行中会比较丑
C="崔崔崔"
ALD="阿拉丁"
require("dotenv").config();
console.log(process.env);
...
new webpack.DefinePlugin({
C: JSON.stringify(process.env.C),
}),
devServer
- 在5版本中,可设置webpack serve
- 会自动查找webpack-dev-server,所以手动安装下
- 它会将编译内容写到内存中,我们可以直接访问
- 还可以通过static属性设置一个静态资源目录,默认为public
"dev": "webpack serve"
history路由需要在服务端接收,否则404
devServer: {
historyApiFallback: true,
},
自定义mock
- onBeforeSetupMiddleware为一个函数,形参为一个server对象,上面的app可以理解为route对象,我们可以同express一样写一些接口
devServer: {
static: path.resolve(__dirname, "public"),
onBeforeSetupMiddleware(devServer) {
devServer.app.get("/users", (req, res) => {
res.json({
id: 1,
name: "cc",
});
});
},
},
loader
eslint
- 代码检测
npm i eslint eslint-loader babel-eslint -D
- 如果想要在ide中高亮错误,那么需要下载ESLint插件
- 如果说只想要提示,但不想阻塞编译,那么久不要加eslint-loader
module: {
rules: [
{
test: /\.js$/,
loader: "eslint-loader",
exclude: /node_modules/,
options: { fix: true },
enforce: "pre",
}]
}
module.exports = {
root: true,
extends: "xxx",
parserOptions: {
sourceType: 'module',
ecmaVersion: 2015,
},
env: {
browser: true,
node: true,
},
rules: {
indent: "off",
quotes: "off",
"no-console": "error",
},
ignorePatterns: ["/dist"],
};
自动修复
- 新建.vscode文件夹
- 进入文件夹,新建settings.json文件
{
"eslint.validate":[
"javascript",
"javascriptreact",
"typescript",
"typescriptreact"
],
"editor.codeActionsOnSave":{
"source.fixAll.eslint":true
}
}
编译分析
- 打包完成是一个执行函数
- 里面有一个
__webpack_modules__对象,它的key就是文件地址,也就是导入路径,value是一个函数,内容是文件内容
- 之所以webpack的打包文件可以在浏览器中执行,是因为它将导入导出模块进行了一个封装
- 我们可以看到有一个
__webpack_require__模块,它代表的就是commonjs的require,执行逻辑与node中的require一致
- 区别在于__dirname以一个变量的形式存储在函数内部
- 内置了缓存机制,读过的文件放在
__webpack_module_cache__里,再次查找会拿路径先找缓存
- 主文件以一个自执行函数放在底部
commonjs示例
- 新建index.js
- 新建title.js
- 按下方示例输入内容,然后build打包
const title = require("./title");
console.log("打印", title);
console.log(__dirname);
module.exports = "title";
(() => {
var __webpack_modules__ = {
"./src/title.js": (module) => {
var __dirname = "/";
console.log(__dirname);
module.exports = "title";
},
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(() => {
const title = __webpack_require__("./src/title.js");
console.log("打印", title);
})();
})();
es6Modul导出,commonjs导入
- 在common基础上拓展,但原理是相同的,封装方法,执行方法
- 这也是在webpack中导入规则可以混用的原因
前置知识补充
Object.prototype.toString会读取值的Symbol.toStringTag属性,如果有的话就直接返回
- webpack封装的时候,将其设置为Modul,表示当前模块为es6Modul
let exports = {};
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
console.log(Object.prototype.toString.call(exports));
示例
export default "title";
export const age = "title_age";
(() => {
var __webpack_modules__ = {
"./src/title.js": (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
"use strict";
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
age: () => age,
default: () => __WEBPACK_DEFAULT_EXPORT__,
});
const __WEBPACK_DEFAULT_EXPORT__ = "title";
const age = "title_age";
},
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
})();
var __webpack_exports__ = {};
(() => {
const title = __webpack_require__("./src/title.js");
console.log("打印", title);
console.log("age", title.age);
})();
})();
为什么使用getter呢
- commonjs导出的是一个值,而es6modul导出的则是一个引用
- 那么在函数执行的时候,将值封装成一个function,fn返回当前作用域中的变量,则可以实现es6modul的这一特性
let age = 1;
exports.age = age;
setTimeout(() => {
age = 200;
}, 1000);
const title = require("./age");
console.log("age", title.age);
setTimeout(() => {
console.log("age", title.age);
}, 2000);
export let age = 1;
setTimeout(() => {
age = 200;
}, 1000);
let age = 1;
let es6modul = {};
Object.defineProperty(es6modul, 'age', { get: () => age });
let commonjsModul = { age: age };
age = 200;
console.log(es6modul.age);
console.log(commonjsModul.age);
es6Modul示例
export default "title";
export const age = "title_age";
import title, { age } from "./title";
console.log(title);
console.log(age);
(() => {
"use strict";
var __webpack_modules__ = {
"./src/title.js": (
__unused_webpack_module,
__webpack_exports__,
__webpack_require__
) => {
__webpack_require__.r(__webpack_exports__);
__webpack_require__.d(__webpack_exports__, {
age: () => age,
default: () => __WEBPACK_DEFAULT_EXPORT__,
});
const __WEBPACK_DEFAULT_EXPORT__ = "title";
const age = "title_age";
},
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
})();
var __webpack_exports__ = {};
(() => {
__webpack_require__.r(__webpack_exports__);
var _title__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/title.js");
console.log(_title__WEBPACK_IMPORTED_MODULE_0__["default"]);
console.log(_title__WEBPACK_IMPORTED_MODULE_0__.age);
})();
})();
commonjs导出,es6Modul导入
- 会增加n方法,会通过
__esModule来判断导入是否为es6Modul,如果是为true,那么default为本身的default,否则就是全部的export
import title, { age } from "./title";
console.log(title);
console.log(age);
module.exports = {
name: "title_name",
age: "title_age",
};
(() => {
var __webpack_modules__ = {
"./src/title.js": (module) => {
module.exports = {
name: "title_name",
age: "title_age",
};
},
};
var __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
var cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
var module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
(() => {
__webpack_require__.n = (module) => {
var getter =
module && module.__esModule ? () => module["default"] : () => module;
return getter;
};
})();
(() => {
__webpack_require__.d = (exports, definition) => {
for (var key in definition) {
if (
__webpack_require__.o(definition, key) &&
!__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
})();
(() => {
__webpack_require__.o = (obj, prop) =>
Object.prototype.hasOwnProperty.call(obj, prop);
})();
(() => {
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
})();
var __webpack_exports__ = {};
(() => {
"use strict";
__webpack_require__.r(__webpack_exports__);
var _title__WEBPACK_IMPORTED_MODULE_0__ =
__webpack_require__("./src/title.js");
var _title__WEBPACK_IMPORTED_MODULE_0___default = __webpack_require__.n(
_title__WEBPACK_IMPORTED_MODULE_0__
);
console.log(_title__WEBPACK_IMPORTED_MODULE_0___default());
console.log(_title__WEBPACK_IMPORTED_MODULE_0__.age);
})();
})();
资源懒加载分析
- 使用import(xxx),会自动分割代码,形成新的文件
- 在入口文件中,只会
引入主文件,然后调用api去引入上述分割文件,形成资源的异步加载,因为初始没有加载全部文件,所以也可以称作懒加载或按需加载
- 好处:spa带来了一个问题,大项目的代码量过大,一次性获取会造成长时间的白屏,分割后白屏情况明显减轻
逻辑走向
- 先加载文件,调用封装好的push方法,将脚本模块内容放到全局modal里,后续引入使用,使promise成功,且删除script标签
- 加载刚才插入的模块,然后将value返回
- 可以获取值了
import("./title").then((res) => {
console.log(res.default);
});
console.log("我是主文件");
(self["webpackChunkwebpack"] = self["webpackChunkwebpack"] || []).push([
["src_title_js"],
{
"./src/title.js": (module) => {
module.exports = {
name: "title_name",
age: "title_age",
};
console.log("我是异步文件");
},
},
]);
let __webpack_modules__ = {};
let __webpack_module_cache__ = {};
function __webpack_require__(moduleId) {
let cachedModule = __webpack_module_cache__[moduleId];
if (cachedModule !== undefined) {
return cachedModule.exports;
}
let module = (__webpack_module_cache__[moduleId] = {
exports: {},
});
__webpack_modules__[moduleId](module, module.exports, __webpack_require__);
return module.exports;
}
__webpack_require__.m = __webpack_modules__;
let getProto = Object.getPrototypeOf
? (obj) => Object.getPrototypeOf(obj)
: (obj) => obj.__proto__;
let leafPrototypes;
__webpack_require__.t = function (value, mode) {
console.log(__webpack_modules__);
console.log(__webpack_module_cache__);
debugger;
if (mode & 1) value = this(value);
if (mode & 8) return value;
if (typeof value === "object" && value) {
if (mode & 4 && value.__esModule) return value;
if (mode & 16 && typeof value.then === "function") return value;
}
let ns = Object.create(null);
__webpack_require__.r(ns);
let def = {};
leafPrototypes = leafPrototypes || [
null,
getProto({}),
getProto([]),
getProto(getProto),
];
for (
let current = mode & 2 && value;
typeof current === "object" && !~leafPrototypes.indexOf(current);
current = getProto(current)
) {
Object.getOwnPropertyNames(current).forEach(
(key) => (def[key] = () => value[key]),
);
}
def.default = () => value;
__webpack_require__.d(ns, def);
return ns;
};
__webpack_require__.d = (exports, definition) => {
for (let key in definition) {
if (
__webpack_require__.o(definition, key)
&& !__webpack_require__.o(exports, key)
) {
Object.defineProperty(exports, key, {
enumerable: true,
get: definition[key],
});
}
}
};
__webpack_require__.f = {};
__webpack_require__.e = (chunkId) => {
const promises = [];
__webpack_require__.f.j(chunkId, promises);
return Promise.all(promises);
};
__webpack_require__.u = (chunkId) => "" + chunkId + ".main.js";
__webpack_require__.g = (function () {
if (typeof globalThis === "object") return globalThis;
try {
return this || new Function("return this")();
} catch (e) {
if (typeof window === "object") return window;
}
}());
__webpack_require__.o = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop);
let inProgress = {};
let dataWebpackPrefix = "webpack:";
__webpack_require__.l = (url, done, key, chunkId) => {
if (inProgress[url]) {
inProgress[url].push(done);
return;
}
let script; var
needAttach;
if (key !== undefined) {
let scripts = document.getElementsByTagName("script");
for (let i = 0; i < scripts.length; i++) {
let s = scripts[i];
if (
s.getAttribute("src") == url
|| s.getAttribute("data-webpack") == dataWebpackPrefix + key
) {
script = s;
break;
}
}
}
if (!script) {
needAttach = true;
script = document.createElement("script");
script.charset = "utf-8";
script.timeout = 120;
if (__webpack_require__.nc) {
script.setAttribute("nonce", __webpack_require__.nc);
}
script.setAttribute("data-webpack", dataWebpackPrefix + key);
script.src = url;
}
inProgress[url] = [done];
let onScriptComplete = (prev, event) => {
script.onerror = script.onload = null;
clearTimeout(timeout);
let doneFns = inProgress[url];
delete inProgress[url];
script.parentNode && script.parentNode.removeChild(script);
doneFns && doneFns.forEach((fn) => fn(event));
if (prev) return prev(event);
};
var timeout = setTimeout(
onScriptComplete.bind(null, undefined, {
type: "timeout",
target: script,
}),
120000,
);
script.onerror = onScriptComplete.bind(null, script.onerror);
script.onload = onScriptComplete.bind(null, script.onload);
needAttach && document.head.appendChild(script);
};
__webpack_require__.r = (exports) => {
if (typeof Symbol !== "undefined" && Symbol.toStringTag) {
Object.defineProperty(exports, Symbol.toStringTag, {
value: "Module",
});
}
Object.defineProperty(exports, "__esModule", {
value: true,
});
};
let scriptUrl;
if (__webpack_require__.g.importScripts) {scriptUrl = __webpack_require__.g.location + "";}
var {document} = __webpack_require__.g;
if (!scriptUrl && document) {
if (document.currentScript) scriptUrl = document.currentScript.src;
if (!scriptUrl) {
let scripts = document.getElementsByTagName("script");
if (scripts.length) scriptUrl = scripts[scripts.length - 1].src;
}
}
if (!scriptUrl) {throw new Error("Automatic publicPath is not supported in this browser");}
scriptUrl = scriptUrl
.replace(/#.*$/, "")
.replace(/\?.*$/, "")
.replace(/\/[^\/]+$/, "/");
__webpack_require__.p = scriptUrl;
let installedChunks = {
main: 0,
};
__webpack_require__.f.j = (chunkId, promises) => {
let installedChunkData = __webpack_require__.o(installedChunks, chunkId)
? installedChunks[chunkId]
: undefined;
if (installedChunkData !== 0) {
if (installedChunkData) {
promises.push(installedChunkData[2]);
} else if (true) {
var promise = new Promise(
(resolve, reject) =>(installedChunkData = installedChunks[chunkId] = [resolve, reject])
);
promises.push((installedChunkData[2] = promise));
var url = __webpack_require__.p + __webpack_require__.u(chunkId);
var error = new Error();
var loadingEnded = (event) => {
if (__webpack_require__.o(installedChunks, chunkId)) {
installedChunkData = installedChunks[chunkId];
if (installedChunkData !== 0) installedChunks[chunkId] = undefined;
if (installedChunkData) {
var errorType =
event && (event.type === "load" ? "missing" : event.type);
var realSrc = event && event.target && event.target.src;
error.message =
"Loading chunk " +
chunkId +
" failed.\n(" +
errorType +
": " +
realSrc +
")";
error.name = "ChunkLoadError";
error.type = errorType;
error.request = realSrc;
installedChunkData[1](error);
}
}
};
__webpack_require__.l(url, loadingEnded, "chunk-" + chunkId, chunkId);
} else installedChunks[chunkId] = 0;
}
};
let webpackJsonpCallback = (parentChunkLoadingFunction, data) => {
let [chunkIds, moreModules, runtime] = data;
let moduleId;
var chunkId;
var i = 0;
console.log('进来');
if (chunkIds.some((id) => installedChunks[id] !== 0)) {
for (moduleId in moreModules) {
if (__webpack_require__.o(moreModules, moduleId)) {
__webpack_require__.m[moduleId] = moreModules[moduleId];
}
}
if (runtime) var result = runtime(__webpack_require__);
}
if (parentChunkLoadingFunction) parentChunkLoadingFunction(data);
for (; i < chunkIds.length; i++) {
chunkId = chunkIds[i];
if (
__webpack_require__.o(installedChunks, chunkId)
&& installedChunks[chunkId]
) {
installedChunks[chunkId][0]();
}
installedChunks[chunkId] = 0;
}
};
let chunkLoadingGlobal = (self.webpackChunkwebpack = self.webpackChunkwebpack || []);
chunkLoadingGlobal.forEach(webpackJsonpCallback.bind(null, 0));
chunkLoadingGlobal.push = webpackJsonpCallback.bind(
null,
chunkLoadingGlobal.push.bind(chunkLoadingGlobal),
);
let __webpack_exports__ = {};
__webpack_require__.e("src_title_js")
.then(__webpack_require__.t.bind(__webpack_require__, "./src/title.js", 23))
.then((res) => {
console.log(res.default);
});
console.log("我是主文件");
简化版伪代码
let module = {};
function require (path) => {
return module[path]();
};
require.e = (path) => {
new Promise((resolve,reject) => {
const script = document.createElement("src");
script.src = path + ".main.js";
script.onload = () => {
module[path] = ()=>{我是异步文件内容}
script.remove();
resolve();
};
document.head.append(script);
})
}
require.e("src_title_js")
.then(require("src_title_js"))
.then(res=>{console.log(res.default,"结果")})