只是根据几个想法,我们便创造出了webpack
打包工具,它能够根据我们在前端项目中遇到的疑难杂症对症下药,那么这一章我们就一起来探讨一下我们项目落地所遇到的种种问题。
前端实践中的问题
- Jsx / Tsx编译问题
- Less / Scss编译问题
- TypeScript编译问题
- 语法 / 环境兼容问题
- 代码压缩问题
- 不同类型的静态资源处理问题
- 模块化文件的统一管理问题
- …
上面的这些问题,都是在开发中遇到的实际问题,每当启动一个项目的时候,我们首先需要去手动tsc
、手动编译jsx
,手动去编译.less
文件等等,初始化的时候我们需要走一遍这样的编译
流程,如果文件中出现一丁点修改
的话,那将必定会再走一遍编译
流程,前端开发苦不堪言,基于此webpack
做的事情,就是把这一套流程所需要的工具,全部集成
起来,让前端开发者只用关注于自己的业务代码,而不用操心其是如何编译的。那webpack
是怎么去解决这些问题的呢?
webpack
可以通过加载多种loaders
来解决编译的问题。webpack
本身机制可以实现将多个js
文件打包成一个或者按需打包成多个文件,实现文件的统一管理问题。webpack
可以通过各种plugins
来解决代码压缩,分割等问题。- 如果是
html
页面所使用到的文件,webpack
也能够提供像原生一样的方式进行模块的加载。
我们知道了这么多的概念与做法,那webpack
具体是怎么实现的呢?
webpack实现模块化的具体实践
我们应当利用webpack
去初始化一个项目,npm init -y
; npm i webpack webpack-cli -S
,我们就会生成如下的package.json
。
{"name": "webpack","version": "1.0.0","description": "","main": "index.js","scripts": {"test": "echo \"Error: no test specified\" && exit 1"},"keywords": [],"author": "","license": "ISC","dependencies": {"webpack": "^5.74.0","webpack-cli": "^4.10.0"}
}
webpack.config.json
const path = require('path')
module.exports = {// 模式mode: 'development',// 入口entry: {// 单入口,或者用数组的形式index: './src/index.js'},// 出口output: {//打包的目录文件夹path: path.resolve(__dirname,'build'),//打包的目录文件名filename: '[name].js',//打包的公共路径,确保在当前域名下可以正常访问publicPath: '/'},// 打包输出源代码,以便于更好的追踪错误devtool: "source-map",
}
这里webpack5
与webpack4
,打包的的信息是不一样的,比如以下:
webpack4
webpack5
entry
在webpack.config.json
里面,我们看到了entry
配置,这个就是配置可以有三种方式:字符串
,数组
,对象
// 配置为数组输出main.js
entry: ['./src/index.js', './src/add.js']
// 配置为对象输出以key命名的的文件
entry: {index:"./src/index.js",add:"./src/add.js"
}
// 配置为字符串输出main.js
entry: './src/index.js',
多入口打包有什么用呢?多入口打包也算作性能优化
的一种吧,他能够帮我们把一个文件所依赖的模块进行另外的输出,从而在某个层面减少了加载一个大文件的网络耗时。
output
在webpack.config.json
里面,我们看到了output
配置,下面我们来讲解一下其中的属性。
output: {//打包的目录文件夹path: path.resolve(__dirname,'build'),//打包的目录文件名filename: '[name].js',//打包的公共路径,确保在当前域名下可以正常访问publicPath: '/'
},
output
是一个对象,其中path
指定的输出文件目录位置,filename
为打生成的文件的名字,我们可以在文件名字后面加上hash
值,这样做的好处有很多,比如利用缓存优化,浏览器网络请求优化等,具体如下:
output: {//打包的目录文件夹path: path.resolve(__dirname,'build'),//打包的目录文件名filename: '[name].[hash].js',//打包的公共路径,确保在当前域名下可以正常访问publicPath: '/'
}
上述代码我们可以实践一下(index.js依赖了add.js)
第一次打包的文件hash
只修改了index.js之后打包的文件hash
只修改了add.js之后打包的文件hash
可见对于hash
来讲,只要项目里面的内容改变了,就会生成新的hash
,如果不想因为index.js
修改影响到了整个文件的hash
值,那么这里就要使用chunkhash
了,还有一种contenthash
用来描述文件内容的hash
值,适用于js
文件中引入css
文件,一般的当js
文件改变,就会重新构建js
与引入的css
文件,contenthash
就可以避免在js
文件改变的同时,会去重新构建css
的情况。
publicPath
属性的作用在于提供一个公共路径,能够保证在服务器上JavaScript
能够正确的访问到资源所在的位置。
mode
webpack
对于mode
,提供了三种基本内置优化预设。
development
:自动优化打包速度,启用内置插件更好的捕捉错误,便于代码调试。production
:启动内置插件优化打包结果,不过打包速度偏慢,便于生产模式访问效率none
:基本上不使用
关于上述mode
的实际配置,参考webpack官网介绍的mode 。通过配置mode
、entry
、output
我们就可以实现一个基本的模块化构建了。
webpack打包的结果分析
我们以build/add.xxx.js
为例简单的分析一下,在打包(webpack5.x
打包版本)的源文件中,我们可以看到他的外层其实就是一个自执行函数。
稍微调试一下
我们可以看到在_webpack_require_
中,这个moduleId
为依赖的模块路径,最后把依赖的模块,通过module
的exports
属性当做对象暴露出去。其中__webpack_module_cache__
为缓存内容,可以在二次调用的时候直接读取,不用重新构建生成。
那_webpack_require_.d
、_webpack_require_.r
、_webpack_require_.o
到底是怎么实现的呢?这些方法由自执行函数包裹起来,以便于每一次执行脚本自动挂到主函数_webpack_require_
上面去。
// __webpack_require__.d
(() => {
__webpack_require__.d = (exports, definition) => {// 如果definition有key而exports中没有
for(var key in definition) {
if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {// 给exports上添加key属性,value为definition[key]
Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
}
}
};
})();
// __webpack_require__.o
(() => {// 用来检测key是不是definition得一个私有属性
__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
})();
// __webpack_require__.r
(() => {
// define __esModule on exports
__webpack_require__.r = (exports) => {// 如果兼容Symbol
if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {// 给exports添加上{[Symbol.toStringTag]:'Module'}
Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
}// 给exports添加上{'__esModule':true}
Object.defineProperty(exports, '__esModule', { value: true });
};
})();
这里的_esModule
是一个什么东西呢?__esModule
是用来兼容ESM
模块导入CommonJS
模块默认导出方案,但是在vscode
编辑器里面单独去执行文件的话,这种方案还是不可以的,可见webpack
之强大。
总结
其实webpack
的使用并不复杂,在webpack4
以后很多的配置都被简化
掉了,还是那句话,作为一个高级前端开发工程师
,我们不仅要关注表层的东西,我们还必须要去深入理解底层机制
与原理
。很多同学觉得难大部分原因是因为没有那种耐心
去过多的关注枯燥
而又乏味
的东西吧。如果同学们也有这种探索底层原理
或者更深层次
的东西的想法,欢迎大家点赞、关注,跟我一起来学习吧。webpack
强大是因为他集成了超多的工具来一起实现前端项目落地,下一章我们将会来探索一下webpack
的loaders
系统
最后
最近还整理一份JavaScript与ES的笔记,一共25个重要的知识点,对每个知识点都进行了讲解和分析。能帮你快速掌握JavaScript与ES的相关知识,提升工作效率。
有需要的小伙伴,可以点击下方卡片领取,无偿分享