针对技术而言,越学越感受不会的太多。多是没到达必定的境界。在网上冲浪学习的时候,看到有些大佬文笔从容,思路清晰。对某个技术点的深度刨析。真产生了一种发自心里的尊重和佩服和尊重和佩服。有些时候,一段文字就能点醒你,原来是这样!!!有时候感受到这东西被嚼碎了往你嘴里塞你都嚼不动的感受(无奈)。本人对于webpack的学习也有一段时间了。想借这篇博文梳理webpack相关的知识体系。也是对于本身学习的一段总结。css
# 像解析Tapable事件流和实现,分析webpack-cli源码,解析构建流程,实现xx带有难度的loader或plugin均都不在本文章之列(都不会)
复制代码
::经过webpack将零散的模块代码打包到同一个JavaScript文件中,对于代码中有环境兼容的问题,经过模块加载器(loader)将其转化,webpack还能够进行代码拆分。对应用中的代码根据须要打包(按需打包),实现了渐进式加载。这样就解决了文件太碎或太大的问题。webpack会以模块化的方式去载入任意类型的文件(import './index.css')。html
webpack解决了前端总体的模块化,而不是单指JavaScript的模块化。全部的资源均可以看作一个模块。前端
import j1 from './index.js'
import c1 from './index.css'
import h1 from './index.html'
import p1 from './index.png'
....
复制代码
下面来看一下vue
webpack是如何知道这是一个模块,我要对他进行打包的呢?java
webpack并不会对入口文件中全部的数据进行无脑打包,而是须要触发方式。react
众所周知,ESModule 的 import 语法会被webpack看成一个模块进行打包。那么还有啥方式?jquery
javaScript代码:Commonjs规范的require语法、AMD的require语法webpack
非javaScript代码:众所周知,非javaScript代码是须要经过loader处理的web
css: 在loader处理css的过程当中,像样式代码中的@import/url 也会触发打包机制。vue-cli
处理css文件时,咱们使用css-loader,css-loader若是发现了@import语法或者url语法会将其引入的路径做为一个新的模块进行打包。好比:background-image: url(background.png); webpack发现了.png文件,会将该文件交给url-loader进行处理。好比@import url(reset.css); webpack发现了.css文件,会出发资源加载,而后将文件交给css-loader处理。
html: 在loader处理html的过程当中,像src也会触发打包机制。若是须要更多的触发机制,须要看loader有没有暴露接口,若是提供,须要本身配置。
只讨论一点:关于对import/require语法的支持。
浏览器是不支持import/require语法的,那么这些语法是怎么被转换了呢?由于webpack提供了基础代码"替换了"import这些语法。 而gulp就是一个纯粹的自动化构建工具流。没有提供这些基础代码让用户轻松的使用模块化语法。
下面来看一下
基础代码或引导代码webpack是如何实现的? 或者说webpack是对import/require等语法的实现?
ps: 如下是在mode: none的时候的打包方式。在mode: development的时候有所不一样。
/******/ (function (modules) { // webpackBootstrap
.....
})
([
/* 0 - 入口文件*/
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _testA_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); // 也就是import testA from './testA.js'
/* harmony import */ var _testB_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(2); // 也就是import testB from './testB.js'
console.log(_testA_js__WEBPACK_IMPORTED_MODULE_0__["default"], _testB_js__WEBPACK_IMPORTED_MODULE_1__["default"]);
}),
/* 1 第一个引入的模块testA*/
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var t1 = 'hello1';
/* harmony default export */ __webpack_exports__["default"] = (t1);
}),
/* 2 第二个引入的模块testB*/
/***/ (function (module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
var t2 = 'hello2';
/* harmony default export */ __webpack_exports__["default"] = (t2);
})
]);
复制代码
从上面能够看到webpack是经过__webpack_require__方法实现了 import x from './x.xx' 的语法。default的意思是默认导出。这个匿名函数的参数是一个数组,每个模块都做为了这个数组中的成员。模块被解析成了一个个函数,从而产生了独立的做用域, 模块与模块之间不会产生变量冲突的问题。 在mode: development的时候,会有一些差别,但大致上是相同的。webpack打包事后提供的引导代码让模块与模块之间的关系清晰独立,并且更容易进行拆分和组合。
总结:
把全部的模块放到同一个文件中,提供基础代码让模块与模块之间的关系保持原有的关系。 将全部的模块都做为巨大匿名函数的数组参数中的成员,数组中的每一个成员都是一个函数,也就是说webpack将每个模块都做为一个函数。以维持模块的私有化。从第一个入口参数开始执行, 只会先执行下标为0的函数。 每个模块都对应一个下标,webpack将模块与模块之间的关系在编译阶段就作好了处理。好比a.js import b.js。 b.js的下标为3, 那么就会在a.js这个函数中,webpack.require(3) , 这样编译好,webpack就是这样维护模块与模块之间的关系的。
webpack能够零配置,默认 是src/index.js --> dist/main.js
webpack会将遇到的全部模块都看成JavaScript去处理
1. 对于同一个资源能够依次使用多个loader
2. 在模块加载的时候工做
复制代码
Loader 专一实现资源模块加载,去实现总体模块的打包
webpack 加强了webpack自动化能力
e g. plugin 能够清除打包的目录、能够拷贝一些资源文件、压缩输出的代码等等等的能力。
webpack的插件机制是由钩子机制实现的。相似于web中的事件,插件的工做过程当中会有不少的环节,为了便于插件的扩展,webpack给每个环节挂载钩子,这样插件的运行和开发就是在钩子中扩展能力。
plugin必须是一个函数或者是一个包含apply方法的对象。
实现plugin是经过在生命周期的钩子中关在函数实现扩展,达到插件的目的。
线上文件引入jquery.min.js, 若是须要须要调试jquery.js的话, 则须要在引入的jquery.min.js最后一行加上
//# sourceMappingURL=jquery-3.4.1.min.map
告诉此文件去寻找jquery-3.4.1.min.map。该文件记录了转换以后的代码和转换以前的代码的映射关系。
目前,webpack对sourcemap的风格支持 有 12种实现方式,每种方式的效率和效果各不相同。
eval模式
eval('console.log(123)') //VM122:1 运行在一个临时的虚拟机上。
eval('console.log(123) //# sourceURL=./foo/bar.js') // ./foo/bar.js:1 运行环境就是./foo/bar.js
// 经过sourceURL 就能够指定该运行环境名称或者说是路径。
复制代码
devtools: eval , 设置成eval模式,能够看到打包后的模块化代码,在打包后的bundle.js中,每一个模块的执行都使用eval包裹执行,在eval的最后能够看到//# sourceURL=webpack:///./src/main.js?
这样的信息来标注文件路径。表明该模块只想源文件的路径。构建的速度最快,可是效果也很通常。只能定位到是哪一个文件有问题。不会生成source map
devtools: eval-source-map, 一样也是使用eval函数执行模块代码,除了能够帮咱们定位到出现问题的文件,还能够肯定行列信息。这种相比较eval,在生成的js内部去生成了以dataURL的形式引入生成的source map。这个sourcemap是通过babel转换的,而不是最原始的。
devtools: cheap-eval-source-map , 在上一个eval-source-map的基础上加了一个cheap,就是廉价的,便宜的,用计算机术语来讲就是阉割版。相比较上一个只能定位到行,可是不能定位到列的信息。可是构建的速度加快了。source map原理同上
cheap-module-eval-source-map, 相比较cheap-eval-source-map, source map映射的真正的源代码,而不是编译后的。会产生 xxx.js.map文件。其余的痛cheap-eval-source-map
inline-source-map, 和 source map 是效果是相同的,可是的.js.map文件是以dataURL的形式放到编译后文件的最后一行。用#sourceMappingURL引入。普通的就是生成 .map.js文件
hidden-source-map, 构建过程当中生成了source map文件, 在代码中没有使用注释的方式引入sourcemap,通常咱们在开发第三方包的时候会使用这个sourcemap风格。
nosources-source-map, 能看到错误出现的位置,可是看不到源代码,只提供错误出现的位置,可是不给你显示,这是在生产环境中防止暴露源代码的一种方案。
选择最佳实践的sourcemap
开发环境下:cheap-module-eval-source-map。
生产环境下:none
生产环境下: nosources-source-map
webpack打包的模块会默认放到output的目录中。
import icon from './icon.png'
// 若是没有publicPath,则icon为 './[hash].png'
// 若是有publicPath,则icon为 publicPath+ './[hahs].png'
复制代码
publicPath
为基准,使用它来决定在哪一个目录下启用服务,来访问 webpack 输出的文件。html页面中资源路径,被自动注入了output中filename的值。
output中filename的值。/backend/js/app.11c8b942e5dd4b3a51b5.js?2c61c09c3e5bff69e658, 自动注入到index.html中做为src/href,可是打包的位置是由path和filename一块儿决定的,也就是须要为filename的结果设置为服务器上做为此项目的根路径。由于只有根路径才能够找到。
若是设置了publicPath,那么全部的静态资源的路径前面都会加上公共路径 publicPath的值。(不会产生目录,只是在引入的路径前加上publicPath的值)
// 设置publicPath: '/abc'
<script src=/abc/backend/js/app.57e28a966ab9582ff286.js?1dcc397cc95f5934ef81></script>
index.html的路径是由filename决定的
也就是说实际在磁盘上产生的路径是由path+filename决定的,可是 代码中的资源地址的路径是filename+publicPath决定的。
对于loader而言,有些loader有本身的publiPath,可是也能够经过设置filename来替换掉publicPath,一劳永逸
复制代码
webpack中最强大的功能之一
模块热替换
应用运行过程当中实时替换某个模块,应用的运行状态不受影响。热替换只将修改的模块实时替换至应用中,没必要去彻底刷新应用。
开启HMR
HMR已经集成到了webpack-dev-Serve了,使用 webpack-dev-Serve --hot 开启热更新。
那么怎样为每一个js单独提供hmr呢? 使用 webpack提供的module.hot.accept(文件路径, () => { // 热替换逻辑 }) , 下面简单的展现下图片的HMR
if (module.hot) {
// 图片的hmr
module.hot.accept('./test.png', () => {
img.src = background
console.log(background)
})
}
复制代码
hotOnly: true // 只使用 HMR,不会 fallback 到 live reloading.
new webpack.DefinePlugin({
// 值要求的是一个代码片断
API_BASE_URL: JSON.stringify('https://api.example.com')
})
复制代码
摇树 , 就是摇掉代码中未被引用的部分(dead-code)。mode:production。会自动开启摇树优化。
tree-shaking的实现:
optimization: {
usedExports: true, // 标记枯树叶
minimize: true // 负责摇掉标记的枯树叶
}
复制代码
let foo = () => {
let x = 1
if (false) {
console.log('never readched')
}
let a = 3
return a
}
let baz = () => {
var x = 'baz is running'
console.log(x)
function unused () {
return 5
}
return x
let c = x + 3
return c
}
module.exports = { // commonjs模块规范导出
baz
}
export { // ESM 模块规范导出
baz
}
复制代码
esModule 规范 打包后的模块
([
function (e, n, r) {
"use strict";
r.r(n);
var t;
t = "baz is running",
console.log(t),
console.log("main.js running")
}
]);
commonjs 规范 打包后的模块
([
function (e, n, t) {
(0, t(1).baz)(), console.log("main.js running")
},
function (e, n, t) {
e.exports = {
baz: function () {
var e = "baz is running";
return console.log(e),
e
}
}
}
]);
复制代码
1. ES6的模块引入是静态分析的,故而能够在编译时正确判断到底加载了什么代码。
2. 分析程序流,判断哪些变量未被使用、引用,进而删除此代码。
复制代码
合并模块函数
optimization: {
usedExports: true, // 标记枯树叶
minimize: true, // 负责摇掉标记的枯树叶
concatenateModules: true. // 开启 Scope Hoisting
}
复制代码
loader: 'babel-loader',
options: {
presets: [
['@babel/preset-env', { modules: 'commonjs' }] // 开启esModule的转换为commonjs, 这样将不会进行commonjs转换。
]
}
复制代码
反作用
容许咱们经过配置的方式去标识咱们的代码是否有反作用,从而为tree-shaking提供更大的压缩空间。
反作用:模块执行时除了导出成员以外所作的事情。好比给xxx原型上添加了原型方法,别人引入的时候会污染到xxx原型。
通常用于npm包标记是否有反作用
如何使用:
使用以前请确保你的代码没有反作用。不然会误删掉反作用代码。
const { CleanWebpackPlugin } = require('clean-webpack-plugin')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
mode: 'none',
entry: {
index: './src/index.js',
album: './src/album.js'
},
output: {
filename: '[name].bundle.js'
},
optimization: {
splitChunks: {
// 自动提取全部公共模块到单独 bundle
chunks: 'all'
}
},
module: {
rules: [
{
test: /\.css$/,
use: [
'style-loader',
'css-loader'
]
}
]
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/index.html',
filename: 'index.html',
chunks: ['index']
}),
new HtmlWebpackPlugin({
title: 'Multi Entry',
template: './src/album.html',
filename: 'album.html',
chunks: ['album']
})
]
}
复制代码
全部动态导入的模块会被自动分包
经过代码的逻辑控制何时须要动态导入,或者说何时加载这个模块。
魔法注释:能够对动态打包出来的文件进行从新命名, 并且能够对文件进行灵活组合。
import('路径').then(data => {}) import() 是ESModule规范的语法,而这个方法返回的是一个promise。
按需加载webpack是如何打包的呢?这是webpack为import() 语法提供的引导代码。
__webpack_require__.e = function requireEnsure(chunkId) {
var promises = [];
var installedChunkData = installedChunks[chunkId];
if(installedChunkData !== 0) { // 0 means "already installed".
// a Promise means "currently loading".
if(installedChunkData) {
promises.push(installedChunkData[2]);
} else {
// setup Promise in chunk cache
var promise = new Promise(function(resolve, reject) {
installedChunkData = installedChunks[chunkId] = [resolve, reject];
});
promises.push(installedChunkData[2] = promise);
// start chunk loading
var script = document.createElement('script'); // 建立一个script标签
var onScriptComplete;
script.charset = 'utf-8';
script.timeout = 120; // 设置script的超时时间
script.src = jsonpScriptSrc(chunkId); // 设置src
// create error before stack unwound to get useful stacktrace later
var timeout = setTimeout(function(){
onScriptComplete({ type: 'timeout', target: script }); // 完成后的逻辑
}, 120000);
script.onerror = script.onload = onScriptComplete;
document.head.appendChild(script); // 插入到页面上
}
}
return Promise.all(promises); // 返回一个promise
};
复制代码
能够看到,动态引入就是建立script,而后获得到script标签的src,将建立好的script标签插入到head里面。
const render = () => {
const hash = window.location.hash || '#posts'
const mainElement = document.querySelector('.main')
mainElement.innerHTML = ''
if (hash === '#posts') {
// mainElement.appendChild(posts())
import(/* webpackChunkName: 'components' */'./posts/posts').then(({ default: posts }) => {
mainElement.appendChild(posts())
})
} else if (hash === '#album') {
// mainElement.appendChild(album())
import(/* webpackChunkName: 'components' */'./album/album').then(({ default: album }) => {
mainElement.appendChild(album())
})
}
}
render()
window.addEventListener('hashchange', render)
复制代码
文件级别的hash,根据文件生成的hash,文件发生修改,hash就会改变。
webpack是大前端发展到如今不能否认的居功至伟的功臣,如今框架开发通常状况都会使用高度开箱即用的脚手架工具,可是对于webpack的了解也是很必要的。理解咱们的程序是如何一步步的从一只满身鸡毛的鸡变成一只香喷喷的奥尔良口味的乾坤烤鸡(drf烤鸡名)