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}变量
。