模块化打包诉求:
- 能够将散落的模块打包到一起;
- 能够编译代码中的新特性;
- 能够支持不同种类的前端资源模块;
- 目前能够很好满足上诉需求的主流工具:Webpack、Parcel、Rollup
webpack
- webpack作为一个模块打包工具,本身就可以解决模块代码打包问题,将零散的js代码打包到一个js文件中。
- 对于环境兼容问题的代码,Webpack可以在打包过程中通过Loader机制对其实现编译转换,然后再进行打包。
- 对于不同类型的前端模块类型,Webpack支持在js代码中以模块形式载入任一类型的资源文件。例如:我们可以通过Webpack实现在js中加载css文件,被加载的css文件将会通过style标签的方式工作。
- Webpack还具有代码的拆分能力,避免打包后的单个文件过大。我们可以把首次加载所必须的模块打包到一起,其他模块再单独打包,等到应用过程中实际需要用到某个模块,再异步加载该模块,实现增量加载,或者叫做渐进式加载,非常适合现代化的大型web应用
Webpack 快速上手
案例:
└─ 02-configuation
│ ├── src
│ │ ├── heading.js
│ │ └── index.js
│ └── index.html
// ./src/heading.js
export default () => {
const element = document.createElement('h2')
element.textContent = 'Hello webpack'
element.addEventListener('click', () => alert('Hello webpack'))
return element
}
// ./src/index.js
import createHeading from './heading.js'
const heading = createHeading()
document.body.append(heading)
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack - 快速上手</title>
</head>
<body>
<script type="module" src="src/index.js"></script>
</body>
</html>
注意:type=“module” 这种用法是ESModules中提出的标准,用来区分普通js脚本和模块
示例中对于支持ES Modules的浏览器可以正常工作,但是对于不支持的浏览器,会出现错误,所以我们需要使用Webpack这样的工具。
- Webpack是一个npm工具模块所以我们先初始化一个package.json文件,用来管理npm依赖版本,完成之后,再来安装Webpack的核心模块以及它的CLI模块,具体操作如下:
$ npm init --yes
$ npm i webpack webpack-cli --save-dev
注:webpack是一个Webpack的核心模块,webpack-cli是Webpack的CLI程序,用来在命令行中调用Webpack。
- webpack-cli所提供的CLI程序就会出现在node_modules/.bin目录当中,我们可以通过npx快速找到CLI并运行它,具体操作如下:
$ npx webpack --version
v4.42.1
注:npx 是 npm 5.2 以后新增的一个命令,可以用来更方便的执行远程模块或者项目 node_modules 中的 CLI 程序。
- 这里我们使用的Webpack版本是v4.42.1,有了Webpack后,就可以直接运行webpack命令来打包js模块代码,具体操作如下:
$ npx webpack
这个命令在执行的过程中,Webpack会自动从src/index.js文件开始打包,然后根据代码中的模块导入操作,自动将所有用到的模块代码打包到一起。
这里我们回到index.html中修改引入文件的路径,由于打包后的代码就不会再有import和export了,所以我们可以删除type=“module”。代码可以正常工作。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Webpack - 快速上手</title>
</head>
<body>
<script src="dist/main.js"></script>
</body>
</html>
我们也可以将Webpack命令定义到npm scripts中,这样每次使用起来会更加方便,具体如下:
{
"name": "01-getting-started",
"version": "0.1.0",
"main": "n/a",
"author": "zhangkai",
"license": "MIT",
"scripts": {
"build": "webpack"
},
"devDependencies": {
"webpack": "^4.42.1",
"webpack-cli": "^3.3.11"
}
}
配置Webpack的打包过程
Webpack 4 以后的版本支持零配置的方式直接启动打包,整个打包过程会按照约定将src/index.js作为打包入口,最终打包的结果会存放到dist/main.js中。
很多时候我们需要自定义路径,例如,在下面这个案例中,我们需要它的打包入口是src/main.js,我们可以通过在项目根目录下添加一个webpack.config.js。具体结构如下:
webpack.config.js
└─ 02-configuation
├── src
├ ├── heading.js
├ └── main.js
├── index.html
├── package.json
└── webpack.config.js ···················· Webpack 配置文件
webpack.config.js是一个运行在Node.js环境中的js文件,也就是说我们需要按照CommonJS的方式编写代码,这个文件可以导出一个对象,我们可以通过所导出对象的属性完成相应的配置选项。
// ./webpack.config.js
module.exports = {
entry: './src/main.js'
}
// ./webpack.config.js
const path = require('path')
module.exports = {
entry: './src/main.js',
output: {
filename: 'bundle.js',
path: path.join(__dirname, 'output')
}
}
注:webpack.config.js是运行在Node.js环境中的代码,所以直接可以使用path之类的Node.js内置模块。
导入 Webpack 配置对象的类型
其目的是为了标注config对象的类型,从而实现智能提示。而在配置完成以后要注释掉这段辅助代码,node环境不知道import语句
由此便衍生了一下写法:
// ./webpack.config.js
/** @type {import('webpack').Configuration} */ //类型注释中使用 import 动态导入类型
const config = {
entry: './src/index.js',
output: {
filename: 'bundle.js'
}
}
module.exports = config
这种写法实现动态载入配置对象类型,这种导入类型不是ES Modules中的所规范的,而是typeScript中提供的特性。虽然我们是以js文件举例啊,并没有涉及typescript,但是在vscode中的类型系统是基于typescript的。
Webpack工作模式
- production模式:启动内置优化插件,自动优化打包结果,打包速度偏慢
- development模式:自动优化打包速度,添加一些调试过程中的辅助插件
- none模式:运行最原始的打包,不做任何处理
如果没有配置明确的值,webpack会给予对应的配置警告,并且默认使用production模式工作
修改Webpack工作模式的方式有两种:
- 通过CLI --mode 参数传入;
- 通过配置文件设置mode属性;
打包结果运行原理
注:将Webpack工作模式设置为none,打包后的结果更加利于理解和阅读
如下:打包后的js文件
把代码全部折叠起来:
TIPS:
- VSCode 中折叠代码的快捷键是 Ctrl + K,Ctrl + 0 (macOS:Command + K,Command + 0)
整体生成的代码其实就是一个立即执行函数,这个函数是Webpack工作入口(webpackBootsstrap),它接收一个modules参数,调用时传入了一个数组
展开下方的数组,图3-4(事实上我本地打包展开是一个对象图3-5,应该是版本不同,作用都是一样的),里面所有的元素均是参数列表相同的函数。这里的函数都对应我们源代码中的模块,在图3-5的版本中,更为明显,通过键值方式映射。每个模块最终都被包括在了这样的函数中,从而实现模块的私有作用域
接下来展开Webpack工作入口函数,图3-6
这个函数最开始定义了一个installedModules对象用于存放或者缓存加载过的模块。紧接着定义了一个require模块,顾名思义,这个函数是加载模块的,再往后就是require函数上挂在了一些其他的数据和工具函数。
这个函数执行到最后调用require函数,传入模块id为0,实际上id就是模块的数组元素下标,对应的是图3-4的数组版本情况,而图3-7中的传入的则是文件路径,无论哪种情况传入的都是入口文件。