本篇文章我会带你从0开始学习webpack,这是webpack系列文章的第一篇,后续会持续更新。
模块化,可以说是当下最重要的前端开发范式之一。随着前端应用的日益复杂化,我们的项目已经逐渐膨胀到了不得不花大量时间去管理的程度。而模块化就是一种最主流的项目组织方式,它通过把复杂的代码按照功能划分为不同的模块单独维护,从而提高开发效率、降低维护成本。
模块化打包需要具有以下的功能
能够将零散的模块打包到一起
能够编译代码中的新特性
能够支持不同类型的前端资源模块
目前主流的打包工具有webpack、Parcel和Rollup。他们都可以满足以上的需求,这里以webpack为例。
webpack作为一个模块打包工具,可以将许多零散的JS文件打包到一个JS文件中
它可以通过loader机制对有环境兼容问题的代码进行编译转换后再打包
它支持载入任意类型的文件,例如可以在JS文件中加载CSS
它具有代码拆分的能力,可以将所有的模块按照我们的需要进行分块打包,这样可以解决避免单个文件过大,加载较慢的问题
我们可以将初次加载所需要的内容打包到一个文件中,其他的模块可以分开打包,等到需要的时候再进行异步加载模块,实现增量加载,也叫渐进式加载。
熟悉VUE的朋友大家都知道,我们习惯了使用VUE-CLI搭建VUE项目,项目中已经帮助我们配置好了webpack的相关配置,基本上我们很少需要去手动的去更改它的配置,因为我们觉得npm run build后可以运行就完了。但是要想成为一个高级的前端工程师,必须要知道如何去使打包后的体积更小,加载更快,提高运行效率。
首先根据以下的结构新建目录
├── src
│ ├── holle.js
│ └── index.js
└── index.html
src/hello.js
export default () =>{
const element = document.createElement('h2')
element.textContent = 'Hello webpack'
element.addEventListener('click', () =>{
alert('Hello webpack')
})
return element
}
src/index.js
imimport hello from './hello'
const sayHello = hello()
document.body.append(sayHello)
index.html
webpack
在上面我们可以看到type=‘module’,这是ES Modules中提出的标准,用来区分加载的是一个脚本还是一个模块,对于支持ES modules标准的浏览器是可以正常运行index.html的,我专门去看了一下现在浏览器对ES Modules支持的情况。ES modules是未来标准发展的一种趋势。
在src/hello.js中导出了一个创建元素的函数,在src/index.js中引入这个模块,并执行创建元素函数,然后添加到body中。我们在开发时进行模块化的开发,可以使结构更加的清晰,功能划分明确,但是在线上环境中,引入众多的模块会显得非常的繁琐,webpack的工作就可以帮助我们把众多的模块合并起来。
首先在工作目录执行
npm init --yes
初始化一个package.json文件,用来管理npm的所有依赖。
安装webpack与webpack-cli
npm install webpack webpack-cli --save
webpack是webpack的核心模块
webpack-cli是Webpack的CLI程序,用来在命令行中调用webpack
$ npx webpack --version
v4.42.1
npx是npm5.2后新增的一个命令,他可以方便的执行远程模块或项目node_modules中的CLI程序,我们刚才安装的webpack-cli就在node_modules中,接下来我们就可以运行webpack命令来进行打包。
$ npx webpack
打包完成后在当前目录或多一个dist文件夹,里面有一个main.js,这个就是打包后的js文件。在控制台也可以看到打包的一些信息,包括打包了那些文件,他们的大小等信息。
现在把index.html中script标签路径替换为打包后的js文件,并去掉type=‘module’,如下所示:
webpack
在浏览器中打开页面,可以看到我们预期的结果
刚才我们就经历了一个webpack的打包过程,看起来也就那样。接下来我们对打包过程加入一些自己的配置。
在当前目录下新建一个webpack.config.js文件,这就是webpack的配置文件,如果这个文件存在,webpack会根据文件中的配置进行打包。
//webpack.config.js
const path = require('path')
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'develpoment.js',
path: path.join(__dirname, 'output')
}
}
这里只写了3个属性,更多的属性可以在webpack的官网中进行查看。
mode(模式):有三个可选值(none/development/production),我们可以根据不用的环境来选择相应的打包方式。
none:运行最原始的打包,不做任何处理
development:自动优化打包速度,添加一些调试过程中的辅助工具
production:启动内置优化插件,优化打包结果,但是打包速度较慢。主要用于生产环境
也可以在cli中以参数的形式选择
webpack --mode=production
entry(入口):指示 webpack 应该使用哪个模块,来作为构建其内部依赖图的开始。进入入口起点后,webpack 会找出有哪些模块和库是入口起点(直接和间接)依赖的。
output(出口):告诉 webpack 在哪里输出它所创建的 bundles,以及如何命名这些文件,默认值为 ./dist
。基本上,整个应用程序结构,都会被编译到你指定的输出路径的文件夹中。通过 output.filename
和 output.path
属性,来告诉 webpack bundle 的名称,以及我们想要 bundle 生成(emit)到哪里。
在上面我们用到了path模块,它是属于node.js的模块,因为webpack.config.js是运行在node.js环境中的代码,所以这里可以使用node.js的内置模块。
如果觉得我的文章对你有有所帮助的话,可以关注我的公众号【前端筱园】,也欢迎访问我的个人网站:www.dzyong.top。
我们把模式设置为none,使用最原始的打包模式,来分析打包后的文件。
把所有的内容先折叠,可以看到打包后的js文件是一个立即执行函数,该函数接收一个数组作为参数。
打开数组参数,可以看到该数组就是我们所需要打包的两个模块。
在函数的内部可以分为四大部分
installedModules用于缓存加载过的模块
__webpack_require__用于加载指定模块的函数
对__webpack_require__函数挂载一些其他的数据和工具函数
最后return中开始加载源代码的入口模块
打开加载模块函数内部,函数接收的moduleId就是数组的下标,从上面的图的最后可以看到,第一次传入的值为0,也就是从第一个模块开始。
经过分析后可以发现其实打包的原理并不难,只是在平时中只知道怎么用就行了,并没有关心过它的过程。就好比VUE一样,现在随便前端人员都会使用VUE,但是当我们知道VUE的底层有原理后,可以帮助我们更好的更灵活的去运用它,遇到问题也可以更快速的定位到问题所在。这就是为什么我们一定要去了解他们的工作原来,也可以帮助我们拓展更广泛的思维。