背景
在webpack打包的时候,我们想把某些包拆分出来,可以使用webpack的externals配置
https://webpack.js.org/configuration/externals
“把某些包拆分出来”,如果不清楚externals的使用,这会让我们产生几个问题:
- 拆分出来后,原来引用包的地方应该怎么写?
- 拆分出来后,拆分的包去哪了?
externals
初始化项目
学习怎么使用externals之前,新建一个webpack项目
新建项目
mkdir webpack-externals-test
cd ./webpack-externals-test
npm init -y
npm i webpack webpack-cli -D2
3
4
5
6
7
新建测试文件
- 根目录下新建
src文件夹,文件夹下新建index.js和test.js:
index.js
import test from './test';
function component() {
let element = document.createElement('div');
element.innerHTML = test('Hello', ' webpack');
return element;
}
document.body.appendChild(component());2
3
4
5
6
7
8
9
10
11
test.js
export default function (a, b) {
// test模块
return a + b;
}2
3
4
- 根目录下新建webpack配置文件
webpack.config.js:
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
devtool: 'none'
};2
3
4
5
6
7
8
9
10
11
增加脚本
package.json中添加脚本
"scripts": {
"build": "webpack"
},2
3
最终项目结构
- node_modules
- src
- - index.js
- - test.js
- package.json
- package-lock.json
- webpack.config.js2
3
4
5
6
7
没有externals的情况
- 运行
npm run build - 查看
dist/bundle.js
/******/ (function(modules) { // webpackBootstrap
/******/ // ... 省略webpack相关代码
/************************************************************************/
/******/ ({
/***/ "./src/index.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _test__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./test */ "./src/test.js");
function component() {
let element = document.createElement('div');
element.innerHTML = Object(_test__WEBPACK_IMPORTED_MODULE_0__["default"])('Hello', ' webpack');
return element;
}
document.body.appendChild(component());
/***/ }),
/***/ "./src/test.js":
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
__webpack_require__.r(__webpack_exports__);
/* harmony default export */ __webpack_exports__["default"] = (function (a, b) {
// test模块
return a + b;
});
/***/ })
/******/ });2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
删除掉一些注释和webpack代码,可以很清晰的看出来:
- 最终的代码都存在一个IIFE中;
- webpack将“获取代码模块”封装成了一个方法
__webpack_require__; - 每个模块路径为key,模块内容为value;
- 源代码中使用
test方法的地方变成了_test__WEBPACK_IMPORTED_MODULE_0__; _test__WEBPACK_IMPORTED_MODULE_0__是通过__webpack_require__获取key为./src/test.js的内容;- 也就是
test.js中的内容
__webpack_exports__["default"] = (function (a, b) {
// test模块
return a + b;
});2
3
4
加externals
在 webpack.config.js 中添加 externals,一定要先看一遍官方文档,再继续往下看。
const path = require('path');
module.exports = {
mode: 'development',
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
externals: {
'./test': '随便写点东西'
},
devtool: 'none'
};2
3
4
5
6
7
8
9
10
11
12
13
14
执行 npm run build,查看 dist/bundle.js
// 。。。省略代码 。。。
/***/ "./test":
/***/ (function(module, exports) {
module.exports = 随便写点东西;
/***/ })2
3
4
5
6
7
8
可以看到该文件对应的之前的test相关的代码发生了改变
- key 从
./src/test.js变成了./test - 内容变成了
module.exports = 随便写点东西;
从这里我们可以看出:
- key 就是externals中的key,value就是
module.exports后面的值; externals默认是global variable形式,也就是上面的“随便写点东西”会被当作全局变量。
对于本文的示例,要正确使用,需要将externals修改一下:
externals: {
'./test': 'commonjs ../src/test.js'
},2
3
再次打包,查看文件
// 。。。省略代码 。。。
/***/ "./test":
/***/ (function(module, exports) {
module.exports = require("../src/test.js");
/***/ })2
3
4
5
6
7
8
这样,test模块就不会被打包进bundle中,调用的时候会通过require获取目标文件。
总结
externals配置可以将
import A from '../B'中 from 后面的模块排除,不打包,并且将这个包对应的来源,改为你定义的来源。
来源可以是commonjs规范的包、commonjs2规范的包、amd规范的包、window上的全局变量。
在这里只用了 externals 的
string形式,还可以用object、function和regex
实践
知道了原理就可以实现很多需求了,比如,我将axios组件封装了一下,打包的时候想排除它,可以将externals写成
externals: {
'axios': 'commonjs axios'
},2
3
这样axios在打包的时候就会被排除,并且引用它的部分会被编译为require('axios')。
这样,就需要要求使用本组件的开发人员必须安装axios组件,可以在package.json的peerDependencies中申明。
在开发组件的过程中,一些公用的工具(utils)、混入(mixins)、配置(config)等等,这些文件就可以单独用babel编译,并通过externals排除打包,指向最终的包路径。
为了方便写externals,就需要用到webpack的alias,给这些文件夹起别名。
resolve: {
alias: {
'@@': path.resolve(__dirname, 'src'),
}
},2
3
4
5
如果需要排除的模块比较多,可以通过fs.readdirSync获取文件夹下的所有文件,获取文件名,存到externals中。
回到问题
拆分出来后,原来引用包的地方应该怎么写? 通过externals配置使用
commonjs还是amd还是global变量拆分出来后,拆分的包去哪了? 这里不算“拆分”,而是排除,包的来源取决于
externals的配置。