[TOC]
# 简介
中文官网:https://webpack.docschina.org/
中文社区:https://github.com/webpack-china/webpack.js.org
Webpack 始于 2012 年,由 Tobias Koppers 发起,用于解决当时现有工具未解决的的一个难题:**构建复杂的单页应用程序 (SPA)。**
# webpack 4 基础
webpack 4 中零配置的概念适用于:
* `entry` point(入口点) 默认为 `./src/index.js
`
* `output`(输出) 默认为 `./dist/main.js`
* production (生产) 和 development (开发) 模式 (无需为生产和开发环境创建2个单独的配置)
这就够了。 但是对于在 webpack 4 中使用 loader (加载器),您仍然需要创建配置文件。
1. 默认打包后的文件是被压缩的
2. 新建配置文件(进行手动配置,**默认配置文件**名为:`webpack.config.js` 或者 `webpackfile.js`)
```
let path=require('path')
module.exports = {
mode:'development', //代表为开发模式 也可以是生产模式 production(打包后的文件为压缩模式)
entry:'./src/index.js', //入口文件
output:{
filename:'bundle.js', //打包输出的文件名
path:path.resolve(__dirname,'dist') // 打包输出的路径(必须是绝对路径)
}
}
```
3. 自定义配置文件名
```
npx webpack --config webpack.config.custom.js
```
# WebPack 是什么
1、一个打包工具
2、一个模块加载工具
3、各种资源都可以当成模块来处理
4、网站 [http://webpack.github.io/](https://link.jianshu.com?t=http%3A%2F%2Fwebpack.github.io%2F)
如今,越来越多的JavaScript代码被使用在页面上,我们添加很多的内容在浏览器里。如何去很好的组织这些代码,成为了一个必须要解决的难题。
对于模块的组织,通常有如下几种方法:
1、通过书写在不同文件中,使用 script 标签进行加载
2、CommonJS进行加载(NodeJS 就使用这种方式)
3、AMD 进行加载(require.js 使用这种方式)
4、ES6 模块
> **思考:为什么只有****JS****需要被模块化管理,前端的很多预编译内容,不需要管理吗?**
![](https://upload-images.jianshu.io/upload_images/1419656-91e4422875a988ff.png?imageMogr2/auto-orient/strip|imageView2/2/w/1190/format/webp)
基于以上的思考,WebPack 项目有如下几个目标:
• 将依赖树拆分,保证按需加载
• 保证初始加载的速度
• 所有静态资源可以被模块化
• 可以整合第三方的库和模块
• 可以构造大系统
## WebPack的特点
1、丰富的插件,方便进行开发工作
2、大量的加载器,包括加载各种静态资源
3、代码分割,提供按需加载的能力
4、发布工具
## WebPack的优势
• webpack 是以 commonJS 的形式来书写脚本滴,但对 AMD/CMD 的支持也很全面,方便旧项目进行代码迁移。
• 能被模块化的不仅仅是 JS 了。
• 开发便捷,能替代部分 grunt/gulp 的工作,比如打包、压缩混淆、图片转 base64 等。
• 扩展性强,插件机制完善,特别是支持 React 热插拔(见 react-hot-loader )的功能让人眼前一亮。
## 模块化打包工具
webpack是一种模块化的工具,每个资源对于webpack来讲都是一个模块,模块之间的引用,它们之间存在的关系,webpack都可以处理好。
1、兼容多种JS模块规范
2、更好地打包静态资源
3、更好地处理模块间的关系
> [引用 Node 模块和 NPM 模块](https://webpack.toobug.net/zh-cn/chapter2/umd.html)
# 深入了解 webpack
## 流程细节
Webpack 的构建流程可以分为以下三大阶段:
1. 初始化:启动构建,读取与合并配置参数,加载 Plugin,实例化 Compiler。
2. 编译:从 Entry 发出,针对每个 Module 串行调用对应的 Loader 去翻译文件内容,再找到该 Module 依赖的 Module,递归地进行编译处理。
3. 输出:对编译后的 Module 组合成 Chunk,把 Chunk 转换成文件,输出到文件系统。
Webpack 打包,最基本的实现方式,是将所有的模块代码放到一个对象(或数组)里,通过属性名(数组ID)对应的路径查找模块,如下所示,可以发现入口`index.js`的代码是放在对应路径值中,其它 `a.js` 和 `b.js` 的代码分别放在了属性名为路径名的对应值中,而 webpack 引用的时候,主要通过`__webpack_require__`的方法引用不同索引的模块。
```
/******/ (function(modules) { // webpackBootstrap
/******/ // The module cache
/******/ var installedModules = {};
/******/
/******/ // The require function
// 定义了一个 require 函数
/******/ function __webpack_require__(moduleId) {
/******/
/******/ // Check if module is in cache
/******/ if(installedModules[moduleId]) {
/******/ return installedModules[moduleId].exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = installedModules[moduleId] = {
// installedModules 模块缓存
/******/ i: moduleId,
/******/ l: false,
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
/******/
/******/ // Flag the module as loaded
/******/ module.l = true;
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/******/
/******/ // expose the modules object (__webpack_modules__)
/******/ __webpack_require__.m = modules;
/******/
/******/ // expose the module cache
/******/ __webpack_require__.c = installedModules;
/******/
/******/ // define getter function for harmony exports
/******/ __webpack_require__.d = function(exports, name, getter) {
/******/ if(!__webpack_require__.o(exports, name)) {
/******/ Object.defineProperty(exports, name, { enumerable: true, get: getter });
/******/ }
/******/ };
/******/
/******/ // define __esModule on exports
/******/ __webpack_require__.r = function(exports) {
/******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
/******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
/******/ }
/******/ Object.defineProperty(exports, '__esModule', { value: true });
/******/ };
/******/
/******/ // create a fake namespace object
/******/ // mode & 1: value is a module id, require it
/******/ // mode & 2: merge all properties of value into the ns
/******/ // mode & 4: return value when already ns object
/******/ // mode & 8|1: behave like require
/******/ __webpack_require__.t = function(value, mode) {
/******/ if(mode & 1) value = __webpack_require__(value);
/******/ if(mode & 8) return value;
/******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;
/******/ var ns = Object.create(null);
/******/ __webpack_require__.r(ns);
/******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value });
/******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));
/******/ return ns;
/******/ };
/******/
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = function(module) {
/******/ var getter = module && module.__esModule ?
/******/ function getDefault() { return module['default']; } :
/******/ function getModuleExports() { return module; };
/******/ __webpack_require__.d(getter, 'a', getter);
/******/ return getter;
/******/ };
/******/
/******/ // Object.prototype.hasOwnProperty.call
/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
/******/
/******/ // __webpack_public_path__
/******/ __webpack_require__.p = "";
/******/
/******/
/******/ // Load entry module and return exports
/******/ return __webpack_require__(__webpack_require__.s = "./src/index.js");
// 启动程序
/******/ })
/************************************************************************/
/******/ ({
/***/ "./src/a.js":
/*!******************!*\
!*** ./src/a.js ***!
\******************/
/*! exports provided: default */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _b__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./b */ \"./src/b.js\");\n\n\n\n\nconsole.log(\"b file value: \"+ _b__WEBPACK_IMPORTED_MODULE_0__[\"value\"]);\n\n/* harmony default export */ __webpack_exports__[\"default\"] = ('a'+ _b__WEBPACK_IMPORTED_MODULE_0__[\"value\"]);\n\n//# sourceURL=webpack:///./src/a.js?");
/***/ }),
/***/ "./src/b.js":
/*!******************!*\
!*** ./src/b.js ***!
\******************/
/*! exports provided: value */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, \"value\", function() { return value; });\n\n\nconst value = 'b';\n\n//# sourceURL=webpack:///./src/b.js?");
/***/ }),
/***/ "./src/index.js":
/*!**********************!*\
!*** ./src/index.js ***!
\**********************/
/*! no exports provided */
/***/ (function(module, __webpack_exports__, __webpack_require__) {
"use strict";
eval("__webpack_require__.r(__webpack_exports__);\n/* harmony import */ var _a__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./a */ \"./src/a.js\");\n\n\nconsole.log(\"Hello Webpack! \" + _a__WEBPACK_IMPORTED_MODULE_0__[\"default\"]);\n\n//# sourceURL=webpack:///./src/index.js?");
/***/ })
/******/ });
```
## 与Grunt、Gulp、browserify的区别
* Grunt/Gulp
1. Grunt/Gulp 工具链、构建工具,可以配合各种插件做js压缩,css压缩,less编译 替代手工实现自动化工作
2. 自动化
3. 提高效率用
4. 可以配置 seajs、requirejs 甚至 webpack的插件。
* webpack 模块化打包
1. webpack 是一种模块化打包工具;
2. 能够将css、js、image打包为一个JS文件;
3. 更智能的模块打包;
4. 更丰富的插件、模块加载器。
* seajs / require
是一种在线"编译" 模块的方案,相当于在页面上加载一个 CMD/AMD 解释器。这样浏览器就认识了 define、exports、module 这些东西。也就实现了模块化。
* browserify
是一个预编译模块的方案,相比于上面 ,这个方案更加智能。
## gulp 结合 Webpack
~~~
import gulpWebpack from 'webpack-stream'
~~~
[https://github.com/shama/webpack-stream](https://github.com/shama/webpack-stream)
## WebPack的配置
每个项目下都可能会有 webpack 配置或是其他形式(如 create-react-app),用来告诉 webpack 它需要做什么。
下面是一个例子:
```
const webpack = require('webpack');
const commonsPlugin = new webpack.optimize.CommonsChunkPlugin('common.js');
module.exports = {
//插件项
plugins:[commonsPlugin],
//页面入口文件配置
entry:{
index : './src/index.js'
},
//入口文件输出配置
output:{
path: 'dist/js/page',
filename: '[name].js'
},
module:{
// 加载器配置
loaders:[
{
test: /\.eot$|\.svg$|\.ttf$|\.woff$/,
// 字体图标最终会以 base64 的方式被打包到 CSS 中
use: [
{
loader: 'url-loader'
}
]
},
{
test: /\.css$/,
use:
[
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 1
}
},
{
loader: 'less-loader',
options: {
noIeCompat: true
}
}
]
},
{
test: /\.js$/,
include,
exclude,
use: "babel-loader",
},
{
test: /\.(png|jpg|gif|svg)$/,
loader: 'file-loader'
}
]
},
// 其它解决方案配置
resolve:{
extensions: [ '.js', '.json', '.scss'],
alias:{
AppStore : 'js/stores/AppStores.js',
ActionType : 'js/actions/ActionType.js',
AppAction : 'js/actions/AppAction.js'
}
}
};
```
1. `plugins` 是插件项,这里我们使用了一个 `CommonsChunkPlugin` 的插件,它用于提取多个入口文件的公共脚本部分,然后生成一个 `common.js` 来方便多页面之间进行复用。
2. `entry` 是页面入口文件配置,`output` 是对应输出项配置 (即入口文件最终要生成什么名字的文件、存放到哪里)
3. `module.loaders` 是最关键的一块配置。它告知 webpack 每一种文件都需要使用什么加载器来处理。 **所有加载器需要使用 npm 加载**
4. 最后是 `resolve` 配置,配置查找模块的路径和扩展名和别名(方便书写)
## Loaders 加载器
webpack 本身只能解析`.js`和`.json`文件,loader 是将其他类型转成**模块**, 以添加到依赖图中。它接收两个属性,分别是`test`和`use`, 前者是一个**正则表达式**, 匹配需要被 loader 转换的文件格式,后者是**使用的 loader 名**.
```
module.exports = {
//...
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, // 不解析 node_modules
include: path.resolve('src'), // 只解析 src 目录下的文件,两者写其一即可
use: {
loader: 'babel-loader',
options: {
presets: ['@babel/preset-env', '@babel/preset-react']
}
}
}
]
}
}
```
对于 loader, 还有两种使用用方式,分别是**内联**和**CLI**, 不过都不常用.
```
// 内联 loader
import Styles from 'style-loader!css-loader?modules!./styles.css';
// CLI loader
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
```
## webpack 常用命令:
```
webpack # 最基本的启动webpack命令
webpack -w # 提供watch方法,实时进行打包更新
webpack -p # 对打包后的文件进行压缩
webpack -d # 提供SourceMaps,方便调试
webpack --config pathToConfigFile # 使用 --config 参数传入路径
webpack --colors # 输出结果带彩色,比如:会用红色显示耗时较长的步骤
webpack --profile # 输出性能数据,可以看到每一步的耗时
webpack --display-modules # 默认情况下node\_modules下的模块会被隐藏,加上这个参数可以显示这些被隐藏的模块
```
## 入口文件配置:entry 参数
entry 可以是字符串(单入口),可以是数组(多入口),但为了后续发展,请务必使用 object,因为 这个 object 中的 key 在 webpack 里相当于此入口的 name,既可以后续用来拼生成文件的路径,也可以用来作为此入口的唯一标识。我推荐的形式是这样的:
```
entry: {
// pagesDir 是前面准备好的入口文件集合目录的路径
'alert/index':path.resolve(pagesDir, `./alert/index/page`),
'index/login':path.resolve(pagesDir, `./index/login/page`),
'index/index':path.resolve(pagesDir, `./index/index/page`),
},
```
对照我的脚手架项目 [webpack-seed](https://link.jianshu.com?t=https%3A%2F%2Fgithub.com%2FArray-Huang%2Fwebpack-seed) 的文件目录结构,就很清楚了:
```
├─src # 当前项目的源码
├─pages # 各个页面独有的部分,如入口文件、只有该页面使用到的 css、模板文件等
│ ├─alert # 业务模块
│ │ └─index # 具体页面
│ ├─index # 业务模块
│ │ ├─index # 具体页面
│ │ └─login # 具体页面
```
由于每一个入口文件都相当于 entry 里的一项,因此这样一项一项地来写实在是有点繁琐,我就稍微写了点代码来拼接这 entry:
```
var pageArr = [
'index/login',
'index/index',
'alert/index',
];
var configEntry = {};
pageArr.forEach((page) => {
configEntry[page] = path.resolve(pagesDir, page +'/page');
});
```
## 输出文件:`output`参数
`output` 参数告诉 webpack 以什么方式来生成/输出文件,值得注意的是,与`entry` 不同,`output`相当于一套规则,所有的入口都必须使用这一套规则,不能针对某一个特定的入口来制定 output 规则。`output`参数里有这几个子参数是比较常用的:path、publicPath、filename、chunkFilename,这里先给个 [webpack-seed](https://github.com/Array-Huang/webpack-seed) 中的示例:
```
output: {
path: buildDir, // var buildDir = path.resolve(__dirname, './build');
publicPath: '../../../../build/',
filename: '[name]/entry.js', // [name] 表示 entry 每一项中的 key,用以批量指定生成后文件的名称
chunkFilename: '[id].bundle.js',
},
```
### `path`
path 参数表示生成文件的根目录,需要传入一个**绝对路径**。
path 参数和后面的 filename 参数共同组成入口文件的完整路径。
### `publicPath`
`publicPath`参数表示的是一个 URL 路径(指向生成文件的根目录),用于生成 css / js /图片/字体文件等资源的路径,以确保网页能正确地加载到这些资源。
publicPath 参数跟 path 参数的区别是:path 参数其实是针对本地文件系统的,而 publicPath 则针对的是浏览器;因此,publicPath 既可以是一个相对路径,如示例中的`../../../../build/,`也可以是一个绝对路径如`http://www.xxxxx.com/`。
一般来说,我还是更推荐相对路径的写法,这样的话整体迁移起来非常方便。那什么时候用绝对路径呢?
其实也很简单,当你的 html 文件跟其它资源放在不同的域名下的时候,就应该用绝对路径了,这种情况非常多见于 CDN 和后端渲染模板的场景。
### `filename`
filename 属性表示的是如何命名生成出来的入口文件,规则有以下三种:
1. `[name]`:指代入口文件的`name`,也就是上面提到的`entry`参数的 `key`,因此,我们可以在`name`里利用 `/`,即可达到控制文件目录结构的效果。
2. `[hash]`:指代**本次编译**的一个`hash`版本(an hash of the compilation. See the [Caching guide](https://devdocs.io/webpack/guides/caching) for details.),值得注意的是,只要是在同一次编译过程中生成的文件,这个 `[hash]` 的值就是一样的;在缓存的层面来说,相当于一次全量的替换。
3. `[chunkhash]`:指代的是当前 chunk 的一个 hash 版本,也就是说,在同一次编译中,每一个 chunk 的 hash 都是不一样的;而在两次编译中,如果某个 chunk 根本没有发生变化,那么该 chunk 的 hash 也就不会发生变化。这在缓存的层面上来说,就是把缓存的粒度精细到具体某个 chunk,只要 chunk 不变,该 chunk 的浏览器缓存就可以继续使用。
下面来说说如何利用 `filename` 参数和 `path` 参数来设计入口文件的目录结构,如示例中的
`path:buildDir, // var buildDir = path.resolve(__dirname, './build');`
和
`filename: '[name]/entry.js'`,
那么对于`key`为 `'index/login'` 的入口文件,生成出来的路径就是 `build/index/login/entry.js` 了,怎么样,是不是很简单呢?
### chunkFilename
`chunkFilename`参数与 `filename` 参数类似,都是用来定义生成文件的命名方式的,只不过,`chunkFilename`参数指定的是除入口文件外的`chunk`(这些`chunk`通常是由于 webpack 对代码的优化所形成的,比如因应实际运行的情况来异步加载)的命名。
# chunk(分片)
webpack也提供了代码分片机制,使我们能够将代码拆分后进行异步加载。
> 值得注意的是,webpack 对代码拆分的定位仅仅是为了解决文件过大,无法并发加载,加载时间过长等问题,并不包括公共代码提取和复用的功能。对公共代码的提取将由`CommonChunks`插件来完成。
要使用webpack的分片功能,首先需要定义“分割点”,即代码从哪里分割成两个文件。具体的方式有两种:
`require.ensure`和 AMD `require`两种方式,来建立分割点,代码在此处被分片。
~~~
var a=require('./a');
a.sayHello();
require.ensure(['./b'], function(require){
var b = require('./b');
b.sayHello();
});
require(['./c'], function(c){
c.sayHello();
});
~~~
打包后的代码:
* bundle.js -> main.js + a.js
* 1.bundle.js -> b.js
* 2.bundle.js -> c.js
> [第三章 webpack进阶-分片](https://webpack.toobug.net/zh-cn/chapter3/chunks.html
## 各种Loader配置:module 参数
webpack的核心实际上也只能针对 js 进行打包,那 webpack 一直号称能够打包任何资源是怎么一回事呢?
原来,webpack拥有一个类似于插件的机制,名为Loader,通过 Loader,webpack 能够针对每一种特定的资源做出相应的处理。Loader 的种类相当多,有些比较基础的是官方自己开发,而其它则是由 webpack 社区开源贡献出来的,这里是 Loader 的List:[list of loaders](https://webpack.docschina.org/loaders/)。
而 module 正是配置什么资源使用哪个 Loader的参数(因为就算是同一种资源,也可能有不同的 Loader 可以使用,当然不同 Loader 处理的手段不一样,最后结果也自然就不一样了)。
### `module.rules` 配置
loaders参数又有几个子参数,先给出一个官方示例:
```
module: {
rules: [
{
// "test" is commonly used to match the file extension
test:/\.jsx$/,
// "include" is commonly used to match the directories
include: [
path.resolve(__dirname,"app/src"),
path.resolve(__dirname,"app/test")
],
// "exclude" should be used to exclude exceptions
// try to prefer "include" when possible
// the "loader"
loader:"babel-loader"
}
]
}
```
下面一一对这些子参数进行说明:
1. test:用来指示当前配置项针对哪些资源,该值应是一个条件值(condition)。
2. exclude:用来剔除掉需要忽略的资源,该值应是一个条件值(condition)。
3. include:用来表示本 loader 配置仅针对哪些目录/文件,该值应是一个条件值(condition)。这个参数跟test参数的效果是一样的(官方文档也是这么写的),我也不明白为嘛有俩同样规则的参数,不过我们姑且可以自己来划分这两者的用途:test 参数用来指示文件名(包括文件后缀),而 include 参数则用来指示目录;注意同时使用这两者的时候,实际上是 and 的关系。
4. loader/use:`ule.loader`是`Rule.use: [ { loader } ]`的简写。详细请查看[`Rule.use`](https://webpack.docschina.org/configuration/module/#rule-use)和[`UseEntry.loader`](https://webpack.docschina.org/configuration/module/#useentry)。
需要注意的是,loader 是可以接受参数的,方式类似于 URL 参数,形如`css?minimize&-autoprefixer`,具体每个loader 接受什么参数请参考 loader 本身的文档(一般也就只能在 github 里看了)。
## 添加额外功能:plugins参数
这 plugins 参数相当于一个插槽位(类型是数组),你可以先按某个 plugin 要求的方式初始化好了以后,把初始化后的实例丢到这里来。
## 图片打包细则
[webpack4 配置 (3)- 打包 css/js/ 图片等资源](https://juejin.im/post/5cb3fa77e51d456e8c1d3c21)
[超详细使用 webpack4.x 搭建标准前端项目](https://zhuanlan.zhihu.com/p/76689742)
## 集成 jQuery
在本项目中比较特殊,因为对于第三方类库统一采用了 dll 单独打包方式,但由于 jQuery 不支持 CDM,所以打包采用 extenal 的形式打包的。
1. 直接引入
我们最常用的引入方式,就是用 AMD 或者 ES6 模块导入的形式在具体的业务模块中直接引入:
```
// header.js
import $ from 'jquery'; // 或者 const $ = require('jquery');
$('h1').hide();
```
如果 webpack 配置文件没有做其他相关设置,那么在这种情况下,jQuery 源码会和业务代码最终会打包到一个文件中。
倘若多个业务模块都引用了 jQuery,则在打包时,webpack 很机智,不会对 jQuery 源码进行多次打包。
即**最终打包的文件,只包含一份 jQuery 源码**。
但在业务代码中需要反复使用`import $ from 'jquery'`来引入 jQuery
2. Webpack 的 ProvidePlugin 插件:
```
// jQuery引用插件
var jQueryProvidePlugin = new webpack.ProvidePlugin({
$: 'jQuery',
jQuery: 'jQuery',
'window.jQuery':'jQuery',
'window.$':'jQuery'
});
```
然后在我们任意源码中:
~~~
// in a module
$('#item'); // <= 起作用
jQuery('#item'); // <= 起作用
// $ 自动被设置为 "jquery" 输出的内容
~~~
3. expose-loader
先把 jQuery对象声明成为全局变量`jQuery`,再通过管道进一步又声明成为全局变量`$`。
~~~
require("expose-loader?$!jquery"); // loader: 'expose?$!expose?jQuery'
~~~
或者,你可以通过配置文件来设置:
~~~
// webpack.config.js
module: {
rules: [{
test: require.resolve('jquery'),
use: [{
loader: 'expose-loader',
options: 'jQuery'
},{
loader: 'expose-loader',
options: '$'
}]
}]
}
~~~
[`require.resolve`](https://nodejs.org/api/modules.html#modules_require_resolve_request_options) 调用是一个 Node.js 函数 (与 webpack 处理流程中的`require.resolve`无关)。`require.resolve`用来获取模块的绝对路径。
4. `externals` 防止被打包
针对第三方库(如 jQuery),我们一般用相对路径或者**类似 CDN 这种**绝对路径的形式,以`<script>`标签在页面里直接引入。这里我们拿 CDN 上的 jQuery 做演示:
~~~
<!-- index.html -->
...
<script src="https://code.jquery.com/jquery-3.1.0.js"></script>
~~~
最后,无需在业务代码中引入第三方库,就能直接使用 jQuery 的 API:
~~~
// header.js
$('h1').hide();
~~~
> [webpack externals 深入理解](https://segmentfault.com/a/1190000012113011)
> [webpack 分离第三方库及公用文件](https://yi-jy.com/2018/06/09/webpack-split-chunks/)
# 开发技巧
> [【Hybrid 开发高级系列】WebPack 模块化专题](https://www.jianshu.com/p/c915685b5c88)
## 多入口配置
多入口的项目 最好是自动获取目录下的入口文件:
```
...
const setMPA = () => {
const entry = {};
const htmlWebpackPlugins = [];
const entryFiles = glob.sync(path.join(__dirname, './src/*/index.js'));
Object.keys(entryFiles)
.map((index) => {
const entryFile = entryFiles[index];
// '/Users/cpselvis/my-project/src/index/index.js'
const match = entryFile.match(/src\/(.*)\/index\.js/);
const pageName = match && match[1];
entry[pageName] = entryFile;
htmlWebpackPlugins.push(
new HtmlWebpackPlugin({
template: path.join(__dirname, `src/${pageName}/index.html`),
filename: `${pageName}.html`,
chunks: [pageName],
inject: true,
minify: {
html5: true,
collapseWhitespace: true,
preserveLineBreaks: false,
minifyCSS: true,
minifyJS: true,
removeComments: false
}
})
);
});
return {
entry,
htmlWebpackPlugins
}
}
const { entry, htmlWebpackPlugins } = setMPA();
module.exports = {
...
```
其实本质就是如下:
```
...
entry:{
'index':'./src/index/index.js', // 页面一的 js
'one':'./src/one/index.js', // 页面二的 js
},
// 输入路径
output: {
path: path.join(__dirname,'../dist'), // 输出的路径
filename: "[name].build.js", // 分别输出不同的 js,【name】是跟上面的 entry 对应
},
plugins:[
new htmlwebpackplugin({
filename: 'index.html',
template: 'src/index/index.html',
chunks: ['index'],
// 选项的作用主要是针对多入口(entry)文件。当你有多个入口文件的时候,对应就会生成多个编译后的 js 文件。
// 那么 chunks 选项就可以决定是否都使用这些生成的 js 文件。
// chunks 默认会在生成的 html 文件中引用所有的 js 文件,当然你也可以指定引入哪些特定的文件。
inject: true,
hash: true
}),
new htmlwebpackplugin({
filename: 'one.html',
template: 'src/one/index.html',
chunks: ['one'],
inject: true,
hash: true
}),
],
...
```
> [《玩转 webpack》极客时间课程源码和课件](https://github.com/cpselvis/geektime-webpack-course)
# 命令构建输出
示例:
~~~
Hash: aafe36ba210b0fbb7073
Version: webpack 4.1.1
Time: 338ms
Built at: 3/16/2018 3:40:14 PM
Asset Size Chunks Chunk Names
main.js 679 bytes 0 [emitted] main
index.html 181 bytes [emitted]
Entrypoint main = main.js
[0] ./src/index.js + 1 modules 219 bytes {0} [built]
| ./src/index.js 77 bytes [built]
| ./src/component.js 142 bytes [built]
Child html-webpack-plugin for "index.html":
1 asset
Entrypoint undefined = index.html
[0] (webpack)/buildin/module.js 519 bytes {0} [built]
[1] (webpack)/buildin/global.js 509 bytes {0} [built]
+ 2 hidden modules
~~~
输出告诉了我们许多:
* `Hash: aafe36ba210b0fbb7073`\- 构建生成的唯一 hash 标志。 你可以`[hash]`来验证静态资源(assets)是否有效。hash 的使用将在[*在文件名中添加 hash*](https://lvzhenbang.github.io/webpack-book/dist/zh/optimizing/04_adding_hashes_to_filenames.html)这章详解。
* `Version: webpack 4.1.1`\- Webpack 的版本。
* `Time: 338ms`\- 构建完成所花费的时间。
* `main.js 679 bytes 0 [emitted] main`\- 生成的静态资源名称、大小、相关联模块的 ID、状态、模块名字。
* `index.html 181 bytes [emitted]`\- 构建过程中生成的另一个静态资源。
* `[0] ./src/index.js + 1 modules 219 bytes {0} [built]`\- 入口静态资源的 ID、名字、大小、入口 ID、生成方式。
* `Child html-webpack-plugin for "index.html":`\- 输出使用的插件。
# 整体配置结构
~~~
const webpack= require('webpack');
const path = require('path');
const TerserPlugin = require('terser-webpack-plugin'); // 混淆压缩 js
module.exports = {
mode: 'production', // (默认值),https://webpack.docschina.org/concepts/mode
// entry 表示 入口,Webpack 执行构建的第一步将从 Entry 开始,可抽象成输入。
// 类型可以是 string | object | array
entry: './app/entry', // 只有1个入口,入口只有1个文件
entry: ['./app/entry1', './app/entry2'], // 只有1个入口,入口有2个文件
entry: { // 有2个入口
a: './app/entry-a',
b: ['./app/entry-b1', './app/entry-b2']
},
// 如何输出结果:在 Webpack 经过一系列处理后,如何输出最终想要的代码。
output: {
// 输出文件存放的目录,必须是 string 类型的绝对路径。
path: path.resolve(__dirname, 'dist'),
// 输出文件的名称
filename: 'bundle.js', // 完整的名称
filename: '[name].js', // 当配置了多个 entry 时,通过名称模版为不同的 entry 生成不同的文件名称
filename: '[chunkhash].js', // 根据文件内容 hash 值生成文件名称,用于浏览器长时间缓存文件
// 发布到线上的所有资源的 URL 前缀,string 类型
publicPath: '/assets/', // 放到指定目录下
publicPath: '', // 放到根目录下
publicPath: 'https://cdn.example.com/', // 放到 CDN 上去
// 导出库的名称,string 类型
// 不填它时,默认输出格式是匿名的立即执行函数
library: 'MyLibrary',
// 导出库的类型,枚举类型,默认是 var
// 可以是 umd | umd2 | commonjs2 | commonjs | amd | this | var | assign | window | global | jsonp ,
libraryTarget: 'umd',
// 是否包含有用的文件路径信息到生成的代码里去,boolean 类型
pathinfo: true,
// 附加 Chunk 的文件名称
chunkFilename: '[id].js',
chunkFilename: '[chunkhash].js',
// JSONP 异步加载资源时的回调函数名称,需要和服务端搭配使用
jsonpFunction: 'myWebpackJsonp',
// 生成的 Source Map 文件名称
sourceMapFilename: '[file].map',
// 浏览器开发者工具里显示的源码模块名称
devtoolModuleFilenameTemplate: 'webpack:///[resource-path]',
// 异步加载跨域的资源时使用的方式
crossOriginLoading: 'use-credentials',
crossOriginLoading: 'anonymous',
crossOriginLoading: false,
},
// 配置模块相关
module: {
rules: [ // 配置 Loader
{
test: /\.jsx?$/, // 正则匹配命中要使用 Loader 的文件
exclude: [ // 不解析这里面的文件
path.resolve(__dirname, 'app/demo-files')
],
include: [ // 只解析这目录下的文件,两者写其一即可
path.resolve(__dirname, 'app')
],
use: [ // 使用那些 Loader,有先后次序,从后往前执行
'style-loader', // 直接使用 Loader 的名称
{
loader: 'css-loader',
options: { // 给 css-loader 传一些参数
}
}
]
},
],
// 防止解析那些任何与给定正则表达式相匹配的文件,忽略的文件中不应该含有任何导入机制。忽略大型的`library`可以提高构建性能。
// 使用正则表达式:noParse: /jquery|lodash/
// 使用函数,从 Webpack 3.0.0 开始支持
noParse: (content)=> {
// content 代表一个模块的文件路径
// 返回 true or false
return /jquery|lodash/.test(content);
}
noParse: /jquery|lodash/
},
// 配置插件
plugins: [
new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/), // 使用`IgnorePlugin`在打包时忽略本地化内容
],
// 配置寻找模块的规则
resolve: {
modules: [ // 寻找模块的根目录,array 类型,默认以 node_modules 为根目录
'node_modules',
path.resolve(__dirname, 'app')
],
extensions: ['.js', '.json', '.jsx', '.css'], // 模块的后缀名
alias: { // 模块别名配置,用于映射模块
// 把 'module' 映射 'new-module',同样的 'module/path/file' 也会被映射成 'new-module/path/file'
'module': 'new-module',
// 使用结尾符号 $ 后,把 'only-module' 映射成 'new-module',
// 但是不像上面的,'module/path/file' 不会被映射成 'new-module/path/file'
'only-module$': 'new-module',
},
alias: [ // alias 还支持使用数组来更详细的配置
{
name: 'module', // 老的模块
alias: 'new-module', // 新的模块
// 是否是只映射模块,如果是 true 只有 'module' 会被映射,如果是 false 'module/inner/path' 也会被映射
onlyModule: true,
}
],
symlinks: true, // 是否跟随文件软链接去搜寻模块的路径
descriptionFiles: ['package.json'], // 模块的描述文件
mainFields: ['main'], // 模块的描述文件里的描述入口的文件的字段名称
enforceExtension: false, // 是否强制导入语句必须要写明文件后缀
},
// 输出文件性能检查配置
performance: {
hints: 'warning', // 有性能问题时输出警告
hints: 'error', // 有性能问题时输出错误
hints: false, // 关闭性能检查
maxAssetSize: 200000, // 最大文件大小 (单位 bytes)
maxEntrypointSize: 400000, // 最大入口文件大小 (单位 bytes)
assetFilter: function(assetFilename) { // 过滤要检查的文件
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
},
devtool: 'source-map', // 配置 source-map 类型
context: __dirname, // Webpack 使用的根目录,string 类型必须是绝对路径
// 配置输出代码的运行环境
target: 'web', // 浏览器,默认
target: 'webworker', // WebWorker
target: 'node', // Node.js,使用 `require` 语句加载 Chunk 代码
target: 'async-node', // Node.js,异步加载 Chunk 代码
target: 'node-webkit', // nw.js
target: 'electron-main', // electron-主线程
target: 'electron-renderer', // electron-渲染线程
externals: { // 使用来自 JavaScript 运行环境提供的全局变量
jquery: 'jQuery'
},
stats: { // 控制台输出日志控制
assets: true,
colors: true,
errors: true,
errorDetails: true,
hash: true,
},
devServer: { // DevServer 相关的配置
proxy: { // 代理到后端服务接口
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'public'), // 配置 DevServer HTTP 服务器的文件根目录
compress: true, // 是否开启 gzip 压缩
historyApiFallback: true, // 是否开发 HTML5 History API 网页
hot: true, // 是否开启模块热替换功能
hotOnly: true // 如果模块热替换功能不生效,则不刷新网页
https: false, // 是否开启 HTTPS 模式
},
profile: true, // 是否捕捉 Webpack 构建的性能信息,用于分析什么原因导致构建性能不佳
cache: false, // 是否启用缓存提升构建速度
watch: true, // 是否开始
watchOptions: { // 监听模式选项
// 不监听的文件或文件夹,支持正则匹配。默认为空
ignored: /node_modules/,
// 监听到变化发生后会等300ms再去执行动作,防止文件更新太快导致重新编译频率太高
// 默认为300ms
aggregateTimeout: 300,
// 判断文件是否发生变化是不停的去询问系统指定文件有没有变化,默认每秒问 1000 次
poll: 1000
},
//===优化 optimization===
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
sourceMap: true,
terserOptions: {
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
}
})
]
},
// 默认配置
splitChunks: {
chunks: 'async', // all 全部(推荐), async 分割异步块, initial
minSize: 30000, // 抽取出来的文件在压缩前的最小大小
maxSize: 0, // 抽取出来的文件在压缩前的最大大小, 默认为 0,表示不限制最大大小
minChunks: 1, // 最小公用模块次数
maxAsyncRequests: 5, // 按需加载时并行请求的最大数量
maxInitialRequests: 3, // 入口点的最大并行请求数
automaticNameDelimiter: '~', // 文件名称分隔符号
// 文件名,值可以是 boolean | function (module, chunks, cacheGroupKey) | string
name: true,
// 缓存策略,默认设置了分割 node_modules 和公用模块
// 会继承splitChunks的配置,但是test、priorty和reuseExistingChunk只能用于配置缓存组
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10 // 优先级
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true // 是否复用存在的 chunk
}
}
}
}
~~~
> [时下最流行前端构建工具Webpack 入门总结](https://mp.weixin.qq.com/s/vdOnXCUGWv6oEEIaKOXFuQ)
# Webpack tricks
[使用Webpack的技巧和窍门](https://github.com/rstacruz/webpack-tricks)
# 参考
[如何使用Webpack创建JavaScript library](https://github.com/iuap-design/blog/issues/323)
https://x-team.com/blog/rollup-webpack-parcel-comparison/
[Webpack的由来](https://survivejs.com/webpack/foreword/)
http://webpack.wuhaolin.cn/
- 讲解 Markdown
- 示例
- SVN
- Git笔记
- github 相关
- DESIGNER'S GUIDE TO DPI
- JS 模块化
- CommonJS、AMD、CMD、UMD、ES6
- AMD
- RequrieJS
- r.js
- 模块化打包
- 学习Chrome DevTools
- chrome://inspect
- Chrome DevTools 之 Elements
- Chrome DevTools 之 Console
- Chrome DevTools 之 Sources
- Chrome DevTools 之 Network
- Chrome DevTools 之 Memory
- Chrome DevTools 之 Performance
- Chrome DevTools 之 Resources
- Chrome DevTools 之 Security
- Chrome DevTools 之 Audits
- 技巧
- Node.js
- 基础知识
- package.json 详解
- corepack
- npm
- yarn
- pnpm
- yalc
- 库处理
- Babel
- 相关库
- 转译基础
- 插件
- AST
- Rollup
- 基础
- 插件
- Webpack
- 详解配置
- 实现 loader
- webpack 进阶
- plugin 用法
- 辅助工具
- 解答疑惑
- 开发工具集合
- 花样百出的打包工具
- 纷杂的构建系统
- monorepo
- 前端工作流
- 爬虫
- 测试篇
- 综合
- Jest
- playwright
- Puppeteer
- cypress
- webdriverIO
- TestCafe
- 其他
- 工程开发
- gulp篇
- Building With Gulp
- Sass篇
- PostCSS篇
- combo服务
- 编码规范检查
- 前端优化
- 优化策略
- 高性能HTML5
- 浏览器端性能
- 前后端分离篇
- 分离部署
- API 文档框架
- 项目开发环境
- 基于 JWT 的 Token 认证
- 扯皮时间
- 持续集成及后续服务
- 静态服务器搭建
- mock与调试
- browserslist
- Project Starter
- Docker
- 文档网站生成
- ddd