
webpack运行原理
webpack维护了一套模块化系统,它兼容所有的模块规范。结合loader和plugin输出最终的编译文件。
一个简单的webpack.config.js配置文件:
1 | var path = require('path') |
以此示例,index.js为入口文件:
1 | //test.js |
打包完成后的目录结构:
├── dist
├── js
├── index-b1a8.js
├── manifest-eef6.js
├── index.html
├── node_modules
├── src
├── js
├── test.js
├── index.html
├── package.json
├── webpack.config.js
.
打包完成后的两个js文件的加载顺序是manifest-eef6.js到index-b1a8.js。先看index.js中的内容:
1 | //index-b1a8.js |
很显然,webpackJsonp是一个全局函数,并且使用了三个参数:
- 数组
- 包含两个方法的对象,
- 以及[“JkW7”]数组。
在以上的第2个参数中,两个方法都包含了相同的参数module、exports,__webpack_require,在打包的原文件中,使用了require及module.exports,而前者此时以__webpack_require__的形式出现。猜测它应该是模块化函数的关键。
在manifest-eef6.js中的代码有151行,经过精简还有28行:
1 | (function(modules) { |
整体的结构是一个IIFE,以空数组作为参数,形参为modules,而在挂载在window的全局函数webpackJsonp中,接受三个参数,分别为chunkIds,moreModules,executeModules,分别对应在index-b1a8.js中的三个参数。
(1)modules通过遍历获取到moreModules内的所有方法,保存在了这个空数组中。
(2)是一个条件判断:
1 | if (executeModules) { |
判断第三个参数是否存在,而按照加载顺序,此时调用window下的全局方法webpackJsonp,因此执行__webpack_require__方法
(3)webpack_require函数
与(1)类似,在它的内部首先是一个赋值的逻辑。如果installedModules中存在对应的moduled,那么直接返回它的exports。而如果没有则需要赋值再返回。赋值的过程为:
1 | modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); |
在(1)中的赋值过程中modules[moduleId]已经得到了moreModules的所有方法,此时的this对象为module.exports,再把module ,module.exports , __webpack_require__传递作为参数调用。而这三个参数正是对应index中出现的三个参数,此后根据module(如test.js中的require部分)所需,递归执行__webpack_require__。在该函数中会判断是否具有缓存,如果具有便不再调用module。webpack打包的文件,就是通过函数隔离module作用域,以达到互不污染的目的。
babel的作用
webpack已经能够胜任ES6 Module的模块化的转化,但仍有ES6语法需要转译,babel可以处理ES6到ES5的转换。
babel能将ES6的模块关键字转换为CommonJS规范。这样就可以直接使用webpack运行时定义的__webpack_require__。在webpack的配置文件中需要配置才可以达到效果。方法有几种,列出其中一种:
(1).首先安装babel系列:
1 | npm install --save-dev babel-loader babel-core babel-preset-latest babel-plugin-transform-runtime |
(2).针对webpack配置文件1
2
3
4
5
6
7
8
9
10//webpack.config.js
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
exclude: /node_modules/
}
]
}
(3).创建.babelrc文件1
2
3
4
5
6
7
8
9//.babelrc 注意该文件形式必须符合JSON格式定义,不能出现注释!!
{
"presets": {
"babel-preset-env"
},
"plugins": {
"transform-runtime"
}
}
ES6的语法1
2
3
4
5
6
7//moduleA.js
export const hello = () => "hello"
export const TYPE = 'animal'
const A = "Milan"
const C = "San Siro"
export default 9
export {A,C}
转换为CommonJS语法:
1 | //moduleA.js |
babel输出都赋值给了exports,而引入时也转换为CommonJS规范,使用require引用模块。所以ES6 Module完全可以和CommonJS规范的模块互用,因为最终都转换为了CommonJS规范的语法。
webpack编译的js如何引用
通过以上配置输出的文件是无法被其他文件引用的。webpack提供了output.libraryTarget配置来定义输出文件的用途,可选项有如下:
- 默认项var
- commonjs
- commonjs2
- AMD
…
1 | //webpack.config.js |
tree-shaking
webpack2开始采用了tree-shaking,即通过静态分析ES6语法,删除无用的模块。但是只对ES Module有效,所以一旦babel将ES module转换成CommonJS语法,就无法使用这一优化。在配置文件中,关闭babel的模块转换功能即可实现。
1 | //webpack.config.js |
修改最初的例子:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15//moduleA.js
export const hello = () => "hello"
export const TYPE = 'animal'
const A = "Milan"
const C = "San Siro"
export default 9
export {A,C}
//moduleB.js
import {hello} from './moduleA'
import a from './moduleA'
import {A} from './moduleA'
console.log(hello())
console.log(A)
console.log(a)
在moduleA.js中输出了{C}变量,但是moduleB.js中并没有引用,所以优化以后moduleA.js将不再输出{C}变量。