[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****需要被模塊化管理,前端的很多預編譯內容,不需要管理嗎?**

基于以上的思考,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