Webpack 是當下最熱門的前端資源模塊化管理和打包工具。它能夠將許多鬆散的模塊按照依賴和規則打包成符合生產環境部署的前端資源。還能夠將按需加載的模塊進行代碼分隔,等到實際須要的時候再異步加載。經過loader的轉換,任何形式的資源均可以視做模塊,好比 CommonJs 模塊、AMD 模塊、ES6 模塊、CSS、圖片、JSON、Coffeescript、LESS 等。javascript
前端是基於多語言、多層次的編碼和組織工做,其次前端產品的交付是基於瀏覽器,這些資源是經過增量加載的方式運行到瀏覽器端,如何在開發環境組織好這些碎片化的代碼和資源,而且保證他們在瀏覽器端快速、優雅的加載和更新,就須要一個模塊化系統。css
Webpack 具備四個核心的概念,想要入門 Webpack 就得先好好了解這四個核心概念。它們分別是Entry(入口)、Output(輸出)、loader 和 Plugins(插件)。接下來詳細介紹這四個核心概念。html
Entry 是 Webpack 的入口起點指示,它指示 webpack 應該從哪一個模塊開始着手,來做爲其構建內部依賴圖的開始。能夠在配置文件(webpack.config.js)中配置 entry 屬性來指定一個或多個入口點,默認爲./src( webpack 4開始引入默認值)。 具體配置方法:前端
entry: string | Array<string>
複製代碼
前者一個單獨的 string 是配置單獨的入口文件,配置爲後者(一個數組)時,是多文件入口。java
//webpack.config.js
module.exports = {
entry: {
app: './app.js',
vendors: './vendors.js'
}
};
複製代碼
以上配置表示從 app 和 vendors 屬性開始打包構建依賴樹,這樣作的好處在於分離本身開發的業務邏輯代碼和第三方庫的源碼,由於第三方庫安裝後,源碼基本就再也不變化,這樣分開打包有利於提高打包速度,減小了打包文件的個數。node
Output 屬性告訴webpack在哪裏輸出它所建立的 bundles,也可指定 bundles 的名稱,默認位置爲 ./dist。整個應用結構都會被編譯到指定的輸出文件夾中去,最基本的屬性包括 filename(文件名)和 path(輸出路徑)。react
值得注意的是,便是你配置了多個入口文件,你也只能有一個輸出點。jquery
具體配置方法:webpack
output: {
filename: 'bundle.js',
path: '/home/proj/public/dist'
}
複製代碼
值得注意的是,output.filename 必須是絕對路徑,若是是一個相對路徑,打包時 webpack 會拋出異常。css3
多個入口時,使用下面的語法輸出多個 bundle :
// webpack.config.js
module.exports = {
entry: {
app: './src/app.js',
vendors: './src/vendors.js'
},
output: {
filename: '[name].js',
path: __dirname + '/dist'
}
}
複製代碼
loader 能夠理解爲webpack的編譯器,它使得webpack能夠處理一些非 JavaScript 文件,好比 png、csv、xml、css、json 等各類類型的文件,使用合適的 loader 可讓 JavaScript 的 import 導入非 JavaScript 模塊。JavaScript 只認爲 JavaScript 文件是模塊,而 webpack 的設計思想即萬物皆模塊,爲了使得 webpack 可以認識其餘「模塊」,因此須要 loader 這個「編譯器」。
webpack 中配置 loader 有兩個目標:
(1)test 屬性:標誌有哪些後綴的文件應該被處理,是一個正則表達式。
(2)use 屬性:指定 test 類型的文件應該使用哪一個 loader 進行預處理。
好比webpack.config.js:
module.exports = {
entry: '...',
output: '...',
module: {
rules: [
{
test: /\.css$/,
use: 'css-loader'
}
]
}
};
複製代碼
該配置文件指示了全部的 css 文件在 import 時都應該通過 css-loader 處理,通過 css-loader 處理後,能夠在 JavaScript 模塊中直接使用 import 語句導入 css 模塊。可是使用 css-loader 的前提是先使用 npm 安裝 css-loader。
此處須要注意的是定義 loaders 規則時,不是定義在對象的 rules 屬性上,而是定義在 module 屬性的 rules 屬性中。
配置多個 loader:
有時候,導入一個模塊可能要先使用多個 loader 進行預處理,這時就要對指定類型的文件配置多個 loader 進行預處理,配置多個 loader,把 use 屬性賦值爲數組便可,webpack 會按照數組中 loader 的前後順序,使用對應的 loader 依次對模塊文件進行預處理。
{
module: {
rules: [
{
test: /\.css$/,
use: [
{
loader: 'style-loader'
},
{
loader: 'css-loader'
}
]
}
]
}
}
複製代碼
loader 用於轉換非 JavaScript 類型的文件,而插件能夠用於執行範圍更廣的任務,包括打包、優化、壓縮、搭建服務器等等,功能十分強大。要是用一個插件,通常是先使用npm包管理器進行安裝,而後在配置文件中引入,最後將其實例化後傳遞給 plugins 數組屬性。
插件是 webpack 的支柱功能,目前主要是解決 loader 沒法實現的其餘許多複雜功能,經過 plugins 屬性使用插件:
// webpack.config.js
const webpack = require('webpack');
module.exports = {
plugins: [
new webpack.optimize.UglifyJsPlugin()
]
}
複製代碼
模式( Mode )能夠經過配置對象的 mode 屬性進行配置,主要值爲 production 或者 development。兩種模式的區別在於一個是爲生產環境編譯打包,一個是爲了開發環境編譯打包。生產環境模式下,webpack 會自動對代碼進行壓縮等優化,省去了配置的麻煩。
學習完以上基本概念以後,基本也就入門 webpack 了,由於 webpack 的強大就是創建在這些基本概念之上,利用 webpack 多樣的 loaders 和 plugins,能夠實現強大的打包功能。
命名空間是經過爲項目或庫建立一個全局對象,而後將全部功能添加到該全局變量中。經過減小程序中全局變量的數量,實現單全局變量,從而在具備大量函數、對象和其餘變量的狀況下不會形成全局污染,同時也避免了命名衝突等問題。
然而,在不一樣的文件中給一個命名空間添加屬性的時候,首先要保證這個命名空間是已經存在的,同時不對已有的命名空間形成任何破壞。能夠經過非破壞性的命名空間函數實現:
var KUI = KUI || {};
KUI.utils = KUI.utils || {};
KUI.utils.namespace = function(ns){
var parts = ns.split("."),
object = KUI,
i, len;
if(parts[0] === "KUI"){
parts = parts.slice(1);
}
for(i = 0, len = parts.length; i < len; i+=1){
if(!object[parts[i]]){
object[parts[i]] = {};
}
object = object[parts[i]];
}
return object;
};
複製代碼
用法:
KUI.utils.namespace("KUI.common");
KUI.utils.namespace("KUI.common.testing");
KUI.utils.namespace("KUI.modules.function.plugins");
KUI.utils.namespace("format");
複製代碼
看一下通過上述後 KUI 都有什麼:
{
"utils": {},
"common": {
"testing": {}
},
"modules": {
"function": {
"plugins": {}
}
},
"format": {}
}
複製代碼
命名空間模式的缺點
1.須要輸入更長的字符,而且須要更長的解析時間; 2.對單全局變量的依賴性,即任何代碼均可以修改該全局實例,其餘代碼將得到修改後的實例。
CommonJS 是 nodejs 也就是服務器端普遍使用的模塊化機制。 該規範的主要內容是,模塊必須經過 module.exports 導出對外的變量或接口,經過 require() 來導入其餘模塊的輸出到當前模塊做用域中。
根據這個規範,每一個文件就是一個模塊,有本身的做用域,文件中的變量、函數、類等都是對其餘文件不可見的。
若是想在多個文件分享變量,必須定義爲 global 對象的屬性。
在每一個模塊內部,module 變量表明當前模塊。它的 exports 屬性是對外的接口,將模塊的接口暴露出去。其餘文件加載該模塊,實際上就是讀取 module.exports 變量。
var x = 5;
var addX = function (value) {
return value + x;
};
module.exports.x = x;
module.exports.addX = addX;
複製代碼
require 方法用於加載模塊,後綴名默認爲.js
var app = require('./app.js');
複製代碼
模塊加載的順序,按照其在代碼中出現的順序
根據參數的不一樣格式,require 命令去不一樣路徑尋找模塊文件。
通常都會有一個主文件(入口文件),在 index.html 中加載這個入口文件,而後在這個入口文件中加載其餘文件。
能夠經過在 package.json 中配置 main 字段來指定入口文件。
第一次加載某個模塊時,Node 會緩存該模塊。之後再加載該模塊,就直接從緩存取出該模塊的 module.exports 屬性。
CommonJS 模塊的加載機制是,輸入的是被輸出的值的拷貝。也就是說,一旦輸出一個值,模塊內部的變化就影響不到這個值。
因爲 CommonJS 是同步加載模塊,這對於服務器端不是一個問題,由於全部的模塊都放在本地硬盤。等待模塊時間就是硬盤讀取文件時間很小。可是,對於瀏覽器而言,它須要從服務器加載模塊,涉及到網速,代理等緣由,一旦等待時間過長,瀏覽器處於」假死」狀態。
AMD 是 "Asynchronous Module Definition" 的縮寫,即 「異步模塊定義」。它採用異步方式加載模塊,模塊的加載不影響它後面語句的運行。
這裏異步指的是不堵塞瀏覽器其餘任務( dom 構建,css 渲染等),而加載內部是同步的(加載完模塊後當即執行回調)。
requirejs 即爲遵循AMD規範的模塊化工具。
RequireJS 的基本思想是,經過 define 方法,將代碼定義爲模塊;經過 require 方法,實現代碼的模塊加載。
RequireJS 主要解決兩個問題:
RequireJS 定義了一個函數 define,它是全局變量,用來定義模塊:
define(id?, dependencies?, factory);
複製代碼
參數說明:
id:指定義中模塊的名字,可選;若是沒有提供該參數,模塊的名字應該默認爲模塊加載器請求的指定腳本的名字。若是提供了該參數,模塊名必須是「頂級」的和絕對的(不容許相對名字)。
依賴 dependencies:是一個當前模塊依賴的,已被模塊定義的模塊標識的數組字面量。 依賴參數是可選的,若是忽略此參數,它應該默認爲["require", "exports", "module"]。然而,若是工廠方法的長度屬性小於 3 ,加載器會選擇以函數的長度屬性指定的參數個數調用工廠方法。
工廠方法 factory,模塊初始化要執行的函數或對象。若是爲函數,它應該只被執行一次。若是是對象,此對象應該爲模塊的輸出值。
define("alpha", ["require", "exports", "beta"], function (require, exports, beta) {
exports.verb = function() {
return beta.verb();
//Or:
return require("beta").verb();
}
});
複製代碼
AMD 也採用 require 命令加載模塊,可是不一樣於 CommonJS ,它要求兩個參數:
require(['math'], function(math) {
math.add(2, 3);
})
複製代碼
第一個參數是一個數組,裏面的成員是要加載的模塊,第二個參數是加載完成後的回調函數。
require 方法自己也是一個對象,它帶有一個 config 方法,用來配置 require.js 運行參數。
require.config({
paths: {
"backbone": "vendor/backbone",
"underscore": "vendor/underscore"
},
shim: {
"backbone": {
deps: [ "underscore" ],
exports: "Backbone"
},
"underscore": {
exports: "_"
}
}
});
複製代碼
paths:paths 參數指定各個模塊的位置。這個位置能夠是同一個服務器上的相對位置,也能夠是外部網址。能夠爲每一個模塊定義多個位置,若是第一個位置加載失敗,則加載第二個位置。上面就是指定了 jquery 的位置,那麼就能夠直接在文件中
require(['jquery'],function($){})
複製代碼
shim:有些庫不是 AMD 兼容的,這時就須要指定 shim 屬性的值。shim 能夠理解成「墊片」,用來幫助require.js 加載非 AMD 規範的庫。
CMD 即Common Module Definition 通用模塊定義,CMD 規範是國內發展出來的,就像 AMD 有個requireJS,CMD 有個瀏覽器的實現 SeaJS,SeaJS 要解決的問題和 requireJS 同樣,只不過在模塊定義方式和模塊加載(能夠說運行、解析)時機上有所不一樣。
在 CMD 規範中,一個模塊就是一個文件。代碼的書寫格式以下:
define(function(require, exports, module) {
// 模塊代碼
});
複製代碼
require 是能夠把其餘模塊導入進來的一個參數; 而 exports 是能夠把模塊內的一些屬性和方法導出的; module 是一個對象,上面存儲了與當前模塊相關聯的一些屬性和方法。
// CMD
define(function(require, exports, module) {
var a = require('./a')
a.doSomething()
// 此處略去 100 行
var b = require('./b') // 依賴能夠就近書寫
b.doSomething()
// ...
})
// AMD 默認推薦的是
define(['./a', './b'], function(a, b) { // 依賴必須一開始就寫好
a.doSomething()
// 此處略去 100 行
b.doSomething()
...
})
複製代碼
ES6 正式提出了內置的模塊化語法,咱們在瀏覽器端無需額外引入 requirejs 來進行模塊化。ES6 在語言標準的層面上,實現了模塊功能,並且實現得至關簡單,徹底能夠取代 CommonJS 和 AMD 規範,成爲瀏覽器和服務器通用的模塊解決方案。
ES6 模塊不是對象,而是經過export命令顯式指定輸出的代碼,再經過 import 命令輸入。
ES6 中的模塊有如下特色:
使用 export 關鍵字將任意變量、函數或者類公開給其餘模塊。
//導出變量
export var color = "red";
export let name = "cz";
export const age = 25;
//導出函數
export function add(num1,num2){
return num1+num2;
}
//導出類
export class Rectangle {
constructor(length, width) {
this.length = length;
this.width = width;
}
}
function multiply(num1, num2) {
return num1 * num2;
}
//導出對象,即導出引用
export {multiply}
複製代碼
重命名想導出的變量、函數或類的名稱
function sum(num1, num2) {
return num1 + num2;
}
export {sum as add}
複製代碼
這裏將本地的 sum 函數重命名爲 add 導出,所以在使用此模塊的時候必須使用 add 這個名稱。
模塊的默認值是使用 default 關鍵字所指定的單個變量、函數或類,而你在每一個模塊中只能設置一個默認導出。
export default function(num1, num2) {
return num1 + num2;
}
複製代碼
此模塊將一個函數做爲默認值進行了導出, default 關鍵字標明瞭這是一個默認導出。此函數並不須要有名稱,由於它就表明這個模塊自身。對比最前面使用 export 導出的函數,並非匿名函數而是必須有一個名稱用於加載模塊的時候使用,可是默認導出則無需一個名字,由於模塊名就表明了這個導出值。
也可使用重命名語法來導出默認值。
function sum(num1, num2) {
return num1 + num2;
}
export { sum as default };
複製代碼
在模塊中使用 import 關鍵字來導入其餘模塊。 import 語句有兩個部分,一是須要導入的標識符,二是需導入的標識符的來源模塊。此處是導入語句的基本形式:
import { identifier1,identifier2 } from "./example.js"
複製代碼
當從模塊導入了一個綁定時,你不能在當前文件中再定義另外一個同名變量(包括導入另外一個同名綁定),也不能在對應的 import 語句以前使用此標識符,更不能修改它的值。
//導入單個綁定
import {sum} from './example.js'
//導入多個綁定
import {sum,multiply} from './example.js'
//徹底導入一個模塊
import * as example from './example.js'
example.sum(1,2);
example.multiply(2,3);
//重命名導入
import { sum as add} from './example.js'
//導入默認值
import sum from "./example.js";
複製代碼
然而要記住,不管你對同一個模塊使用了多少次 import 語句,該模塊都只會被執行一次。
在導出模塊的代碼執行以後,已被實例化的模塊就被保留在內存中,並隨時都能被其餘 import 所引用.
import { sum } from "./example.js";
import { multiply } from "./example.js";
import { magicNumber } from "./example.js";
複製代碼
儘管此處的模塊使用了三個 import 語句,但 example.js 只會被執行一次。若同一個應用中的其餘模塊打算從 example.js 導入綁定,則那些模塊都會使用這段代碼中所用的同一個模塊實例。
export 與 import 都有一個重要的限制,那就是它們必須被用在其餘語句或表達式的外部,而不能使用在if等代碼塊內部。緣由之一是模塊語法須要讓 JS 能靜態判斷須要導出什麼,正由於此,你只能在模塊的頂級做用域使用 export 與 import。
webpack 對各類模塊化的支持
// app.js
// es module
import sum from './sum'
// commonjs
var minus = require('./minux')
//amd
require(['muti'], function () {
console.log(muti(2, 3))
})
console.log(sum(2, 3))
console.log(minus(3, 2))
複製代碼
// sum.js
export default function () {
return a + b
}
複製代碼
// minus.js
module.exports = function (a, b) {
a - b
}
複製代碼
// muti.js
define(function() {
'use strict';
return function (a, b) {
return a * b;
}
});
複製代碼
如今你寫的 JS 代碼,在上線以前,都是須要進行壓縮的,在沒有 webpack 和 gulp 這些工具前,你可能須要找一個壓縮軟件或者在線進行壓縮,在Webpack中能夠很輕鬆的實現JS代碼的壓縮,它是經過插件的方式實現的,這裏咱們就先來引入一個 uglifyjs-webpack-plugin ( JS 壓縮插件,簡稱 uglify)。
注意:雖然 uglifyjs 是插件,可是webpack版本里默認已經集成,不須要再次安裝。
引入:
咱們須要在 webpack.config.js 中引入 uglifyjs-webpack-glugin 插件
const uglify = require('uglifyjs-webpack-plugin');
複製代碼
引入後在 plugins 配置裏new一個 uglify 對象就能夠了,代碼以下。
plugins:[
new uglify()
],
複製代碼
這時候在終端中使用 webpack 進行打包,你會發現 JS 代碼已經被壓縮了。
在前端開發中都開始使用ES6的語法了,雖說 webpack3 增長了一些 ES6 的轉換支持,可是實際效果不是很好。因此我在開發中仍是喜歡添加 Babel-loader 的,我也查看了一些別人的 webpack 配置也都增長了 babel-loader,因此這節課咱們學習一下如何增長 Babel 支持。
Babel 是什麼? Babel 實際上是一個編譯 JavaScript 的平臺,它的強大之處表如今能夠經過便宜幫你達到如下目的:
Babel 實際上是幾個模塊化的包,其核心功能位於稱爲 babel-core 的 npm 包中,webpack 能夠把其不一樣的包整合在一塊兒使用,對於每個你須要的功能或拓展,你都須要安裝單獨的包(用得最多的是解析 ES6 的 babel-preset-es2015 包和解析 JSX 的 babel-preset-react 包)。
安裝依賴包
npm install --save-dev babel-loader babel-core babel-preset-env
複製代碼
在 webpack 中配置 Babel 的方法以下:
{
test:/\.(jsx|js)$/,
use:{
loader:'babel-loader',
options:{
presets:[
"es2015","react"
]
}
},
exclude:/node_modules/
}
複製代碼
雖然 Babel 能夠直接在 webpack.config.js 中進行配置,可是考慮到 babel 具備很是多的配置選項,若是卸載 webapck.config.js 中會很是的雍長不可閱讀,因此咱們常常把配置卸載 .babelrc 文件裏。
在項目根目錄新建 .babelrc 文件,並把配置寫到文件裏。
. babelrc
{
"presets":["react","es2015"]
}
複製代碼
.webpack.config.js 裏的 loader 配置
{
test:/\.(jsx|js)$/,
use:{
loader:'babel-loader',
},
exclude:/node_modules/
}
複製代碼
babel-preset-env 代替 babel-preset-ES2015 , babel 官方推出了 babel-preset-env ,並建議在使用的時候選擇 env 代替以前的 ES20** 。env 爲咱們提供了更智能的編譯選擇。
npm install --save-dev babel-preset-env
複製代碼
而後修改 .babelrc 裏的配置文件。其實只要把以前的 es2015 換成 env 就能夠了。
{
"presets":["react","env"]
}
複製代碼
CommonsChunkPlugin 插件,是一個可選的用於創建一個獨立文件 (又稱做 chunk ) 的功能,這個文件包括多個入口 chunk 的公共模塊。
經過將公共模塊拆出來,最終合成的文件可以在最開始的時候加載一次,便存到緩存中供後續使用。這個帶來速度上的提高,由於瀏覽器會迅速將公共的代碼從緩存中取出來,而不是每次訪問一個新頁面時,再去加載一個更大的文件。
生成一個額外的 chunk 包含入口 chunk 的公共模塊。
new webpack.optimize.CommonsChunkPlugin({
name: "commons",
// ( 公共chunk(commnons chunk) 的名稱)
filename: "commons.js",
// ( 公共chunk 的文件名)
// minChunks: 3,
// (模塊必須被3個 入口 chunk 共享)
// chunks: ["pageA", "pageB"],
// (只使用這些 入口chunk)
})
複製代碼
你必須在 入口 chunk 以前加載生成的這個公共 chunk:
<script src="commons.js" charset="utf-8"></script>
<script src="entry.bundle.js" charset="utf-8"></script>
複製代碼
將你的代碼拆分紅公共代碼和應用代碼。
entry: {
vendor: ["jquery", "other-lib"],
app: "./entry"
},
plugins: [
new webpack.optimize.CommonsChunkPlugin({
name: "vendor",
// filename: "vendor.js"
// (給 chunk 一個不一樣的名字)
minChunks: Infinity,
// (隨着 entry chunk 愈來愈多,
// 這個配置保證沒其它的模塊會打包進 vendor chunk)
})
]
複製代碼
使用代碼拆分功能,一個 chunk 的多個子 chunk 會有公共的依賴。爲了防止重複,能夠將這些公共模塊移入父 chunk。這會減小整體的大小,但會對首次加載時間產生不良影響。若是預期到用戶須要下載許多兄弟 chunks(例如,入口 trunk 的子 chunk),那這對改善加載時間將很是有用。
new webpack.optimize.CommonsChunkPlugin({
// names: ["app", "subPageA"]
// (選擇 chunks,或者忽略該項設置以選擇所有 chunks)
children: true,
// (選擇全部被選 chunks 的子 chunks)
// minChunks: 3,
// (在提取以前須要至少三個子 chunk 共享這個模塊)
})
複製代碼
與上面的相似,可是並不是將公共模塊移動到父 chunk(增長初始加載時間),而是使用新的異步加載的額外公共chunk。當下載額外的 chunk 時,它將自動並行下載。
new webpack.optimize.CommonsChunkPlugin({
name: "app",
// or
names: ["app", "subPageA"]
// the name or list of names must match the name or names
// of the entry points that create the async chunks
children: true,
// (選擇全部被選 chunks 的子 chunks)
async: true,
// (建立一個異步 公共chunk)
minChunks: 3,
// (在提取以前須要至少三個子 chunk 共享這個模塊)
})
複製代碼
webpack 能夠幫助咱們將代碼分紅不一樣的邏輯塊,在須要的時候加載這些代碼。
require.ensure() 是一種使用 CommonJS 的形式來異步加載模塊的策略。在代碼中經過 require.ensure([]) 引用模塊,其使用方法以下:
require.ensure(dependencies: String[], callback: function(require), chunkName: String); 複製代碼
第一個參數指定依賴的模塊,第二個參數是一個函數,在這個函數裏面你可使用 require 來加載其餘的模塊,webpack 會收集 ensure 中的依賴,將其打包在一個單獨的文件中,在後續用到的時候使用 jsonp 異步地加載進去。
//進行代碼分割
require.ensure(['lodash'],function(){
var _ = require('lodash');//上邊的require.ensure只會引入進來,可是並不會執行,再次require纔會執行。
},'vendor')
複製代碼
或者
if(page=='subPageA'){
require.ensure(['./subPageA'],function(){
var subPageA=require('subPageA');
},'subPageA')
}else if(page=='subPageB'){
require.ensure(['./subPageB'],function(){
var subPageA=require('subPageB');
},subPageB)
}
複製代碼
或者
require.ensure(['./subPageA','./subPageB'],function(){
var subPageA=require('subPageA');
var subPageB=require('subPageB');
},common)
//common表示這個模塊的名字
複製代碼
可是僅僅這樣配置並不能把公共 js 抽離出來,在多頁面應用中能夠經過 new webpack.optimize.CommonsChunkPlugin 這個 plugin 來實現,可是對於單頁面來講,就須要藉助 require.include 了
require.include('./moduleA')
if(page=='subPageA'){
require.ensure(['./subPageA'],function(){
var subPageA=require('subPageA');
},'subPageA')
}else if(page=='subPageB'){
require.ensure(['./subPageB'],function(){
var subPageA=require('subPageB');
},subPageB)
}
複製代碼
這樣就會把公共模塊 moduleA 給抽離出來。
import 與 require.ensure 最大的區別就是,他在引入的時候會直接執行,而不須要在此 require 了
import('./subPageA').then(function(){
})
複製代碼
可是這樣打包出來的是沒有 chunkname 的,怎麼添加 chunkname 呢?須要 webpack3+ 的魔法註釋
import(/*webpackChunkName:'subPageA'*/'./subPageA').then(function(){
})
複製代碼
首先,在 src 目錄下創建 css 文件夾,和 index.css 文件,並編寫以下代碼:
body{
background: burlywood;
color:white;
font-size:30px;
}
複製代碼
創建好後,須要引入到入口文件中,才能夠打包。在 entery.js 的首行加入代碼:
import css from './css/index.css';
複製代碼
CSS 和引入作好後,咱們就須要使用 loader 來解析 CSS 文件了,這裏咱們須要兩個解析用的 loader,分別是 style-loader 和 css-loader。
它是用來處理 css 文件中的 url() 等。 用 npm install 進行項目安裝:
npm install --save-dev style-loader
複製代碼
它是用來將 css 插入到頁面的 style 標籤。 用 npm install 進行項目安裝:
npm install --save-dev css-loader
複製代碼
修改 webpack.config.js 中 module 屬性中的配置代碼以下:
webpack.config.js
module:{
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
}
]
},
複製代碼
目前,打包後的文件中,css 是打包在 js 代碼裏面的,這樣不便於之後的維護,因此須要把 CSS 從 js 中分離出來,咱們須要使用插件 Extract Text Plugin。
安裝:
npm install --save-dev extract-text-webpack-plugin
複製代碼
在 webpack.config.js 中引入
const ExtractTextPlugin = require('extract-text-webpack-plugin');
複製代碼
在 Plugins中配置:
new ExtractTextPlugin('css/index.css');
//css/index.css是分離後的路徑位置
複製代碼
修改 Loader 配置:
module:{
rules:[
{
test:/\.css$/,
use:ExtractTextPlugin.extract({
fallback:"style-loader",
use:"css-loader"
})
}
]
}
複製代碼
Less 做爲目前很火的 CSS 預處理語言,它擴展了 CSS 語言,增長了變量、Mixin 、函數等特性,使 CSS 更易維護和擴展;
安裝:
npm install --save-dev less less-loader
複製代碼
在 webpack.config.js 中配置 Loader:
module:{
rules:[
{
test:/\.less$/,
use:ExtractTextPlugin.extract({
fallback:"style-loader",
use:[{
loader:"css-loader"
},{
loader:"less-loader"
}]
})
}
]
}
複製代碼
Sass 的打包和分離和 less 的相似,首先下載安裝 Sass 所支持的服務與 loader。 安裝:
npm install --save-dev node-sass sass-loader
複製代碼
在 webpack.config.js 中配置 Loader:
module:{
rules:[
{
test:/\.less$/,
use:ExtractTextPlugin.extract({
fallback:"style-loader",
use:[{
loader:"css-loader"
},{
loader:"sass-loader"
}]
})
}
]
}
複製代碼
CSS3 是目前做爲一個前端必需要掌握的技能,可是因爲如今好多瀏覽器仍是不兼容 CSS3,因此前端須要多寫很醜很難看的前綴代碼;之前都是邊查 Can I Use ,邊添加,這樣很麻煩,如今配置一個插件 postcss就能夠搞定;
PostCSS 是一個 CSS 的處理平臺,它能夠幫助你的 CSS 實現更多的功能,可是今天咱們就經過其中的一個加前綴的功能,初步瞭解一下 PostCSS。
安裝:
npm install --save-dev postcss-loader autoprefixer
複製代碼
在根目錄下,創建一個 postcss.config.js 文件:
module.exports = {
plugins:[
require('autoprefixer')
]
}
複製代碼
這就是對 postCSS 一個簡單的配置,引入了 autoprefixer 插件。讓 postCSS 擁有添加前綴的能力,它會根據 can i use 來增長相應的css3屬性前綴。
在 webpack.config.js 中配置 Loader:
{
test: /\.css$/,
use: extractTextPlugin.extract({
fallback: 'style-loader',
use: [
{ loader: 'css-loader',
options: { importLoaders: 1 }
},
'postcss-loader'
]
})
}
複製代碼
Tree-shaking 字面意思就是搖晃樹, 其實就是去除那些引用的但卻沒有使用的代碼。 Tree-shaking 概念最先由 Rollup.js 提出,後來在 webpack2 中被引入進來,可是這個這一特性可以被支持得益於 ES6 modules 的靜態特性。ES6的模塊聲明相比於傳統 CommonJS 的同步 require 有着本質區別。這種 modules 設計保證了依賴關係是提早肯定的,使得靜態分析成爲了可能,與運行時無關。 而且 webpack 中並無直接對 tree-shaking 的配置,須要藉助 uglifyjs-webpack-plugin。
webpack 中 tree-shaking主要分爲兩個方面:
將文件標記爲無反作用( side-effect-free ) 在一個純粹的 ESM 模塊世界中,識別出哪些文件有反作用很簡單。然而,咱們的項目沒法達到這種純度,因此,此時有必要向 webpack 的 compiler 提供提示哪些代碼是「純粹部分」。
這種方式是經過 package.json 的 "sideEffects" 屬性來實現的。
{
"name": "your-project",
"sideEffects": false
}
複製代碼
如同上面提到的,若是全部代碼都不包含反作用,咱們就能夠簡單地將該屬性標記爲 false,來告知 webpack,它能夠安全地刪除未用到的 export 導出。
「反作用」的定義是,在導入時會執行特殊行爲的代碼,而不是僅僅暴露一個 export 或多個 export 。舉例說明,例如 polyfill ,它影響全局做用域,而且一般不提供 export 。
若是你的代碼確實有一些反作用,那麼能夠改成提供一個數組:
{
"name": "your-project",
"sideEffects": [
"./src/some-side-effectful-file.js"
]
}
複製代碼
壓縮輸出 經過如上方式,咱們已經能夠經過 import 和 export 語法,找出那些須要刪除的「未使用代碼(dead code)」,然而,咱們不僅是要找出,還須要在 bundle 中刪除它們。爲此,咱們將使用 -p(production) 這個 webpack 編譯標記,來啓用 uglifyjs 壓縮插件。
注意,--optimize-minimize 標記也會在 webpack 內部調用 UglifyJsPlugin。 從 webpack 4 開始,也能夠經過 "mode" 配置選項輕鬆切換到壓縮輸出,只需設置爲 "production"。
webpack.config.js
const path = require('path');
module.exports = {
entry: './src/index.js',
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
mode: "production"
};
複製代碼
爲了學會使用 tree shaking,你必須……
像 Bootstrap 這樣的框架每每會帶有不少 CSS。在項目中一般咱們只使用它的一小部分。就算咱們本身寫CSS,隨着項目的進展,CSS 也會愈來愈多,有時候需求更改,帶來了 DOM 結構的更改,這時候咱們可能無暇關注 CSS 樣式,形成不少 CSS 的冗餘。
PurifyCSS 使用 PurifyCSS 能夠大大減小 CSS 冗餘,好比咱們常用的 BootStrap (140KB)就能夠減小到只有 35KB 大小。這在實際開發當中是很是有用的。
安裝 PurifyCSS-webpack 從名字你就能夠看出這是一個插件,而不是 loader。因此這個須要安裝還須要引入。 PurifyCSS-webpack 要以來於 purify-css 這個包,因此這兩個都須要安裝。
npm i –save-dev purifycss-webpack purify-css
複製代碼
引入 glob 由於咱們須要同步檢查html模板,因此咱們須要引入 node 的 glob 對象使用。在 webpack.config.js 文件頭部引入 glob。
const glob = require('glob');
複製代碼
引入 purifycss-webpack 一樣在 webpack.config.js 文件頭部引入 purifycss-webpack
const PurifyCSSPlugin = require("purifycss-webpack");
複製代碼
配置 plugins 引入完成後咱們須要在 webpack.config.js 裏配置 plugins 。代碼以下,重點看標黃部分。
plugins:[
//new uglify()
new htmlPlugin({
minify:{
removeAttrubuteQuotes:true
},
hash:true,
template:'./src/index.html'
}),
new extractTextPlugin("css/index.css"),
new PurifyCSSPlugin({
// Give paths to parse for rules. These should be absolute!
paths: glob.sync(path.join(__dirname, 'src/*.html')),
})
]
複製代碼
這裏配置了一個 paths ,主要是需找 html 模板,purifycss 根據這個配置會遍歷你的文件,查找哪些css 被使用了。
配置好上邊的代碼,咱們能夠故意在 css 文件裏寫一些用不到的屬性,而後用 webpack 打包,你會發現沒用的 CSS 已經自動給你刪除掉了。在工做中記得必定要配置這個 plugins ,由於這決定你代碼的質量,很是有用。
在 index.html 文件中增長一個放置 div 的標籤
<div id="tupian"></div>
複製代碼
編寫 css 文件,把圖片做爲背景顯示。
#tupian{
background-image: url(../images/manhua.png);
width:466px;
height:453px;
}
複製代碼
安裝 file-loader 和 url-loader
npm install --save-dev file-loader url-loader
複製代碼
file-loader :解決引用路徑的問題,拿 background 樣式用 url 引入背景圖來講,咱們都知道, webpack 最終會將各個模塊打包成一個文件,所以咱們樣式中的 url 路徑是相對入口 html 頁面的,而不是相對於原始 css 文件所在的路徑的。這就會致使圖片引入失敗。這個問題是用 file-loader 解決的,file-loader 能夠解析項目中的 url 引入(不只限於 css),根據咱們的配置,將圖片拷貝到相應的路徑,再根據咱們的配置,修改打包後文件引用路徑,使之指向正確的文件。 url-loader:若是圖片較多,會發不少 http 請求,會下降頁面性能。這個問題能夠經過 url-loader 解決。url-loader 會將引入的圖片編碼,生成 dataURl 。至關於把圖片數據翻譯成一串字符。再把這串字符打包到文件中,最終只須要引入這個文件就能訪問圖片了。固然,若是圖片較大,編碼會消耗性能。所以url-loader 提供了一個 limit 參數,小於 limit 字節的文件會被轉爲 DataURl ,大於 limit 的還會使用 file-loader 進行 copy。
配置 url-loader 咱們安裝好後,就可使用這個 loader 了,記得在 loader 使用時不須要用 require 引入,在plugins 才須要使用 require 引入。
webpack.config.js文件
//模塊:例如解讀 CSS,圖片如何轉換,壓縮
module:{
rules: [
{
test: /\.css$/,
use: [ 'style-loader', 'css-loader' ]
},{
test:/\.(png|jpg|gif)/ ,
use:[{
loader:'url-loader',
options:{
limit:500000
}
}]
}
]
},
複製代碼
有的小夥伴會發現咱們並無在 webpack.config.js 中使用 file-loader ,可是依然打包成功了。咱們須要瞭解 file-loader 和 url-loader 的關係。url-loader 和 file-loader 是什麼關係呢?簡答地說,url-loader 封裝了 file-loader 。 url-loader 不依賴於 file-loader ,即便用 url-loader 時,只須要安裝 url-loader 便可,不須要安裝 file-loader ,由於 url-loader內置了 file-loader 。經過上面的介紹,咱們能夠看到,url-loader 工做分兩種狀況:
1.文件大小小於 limit 參數, url-loader 將會把文件轉爲 DataURL( Base64格式 );
2.文件大小大於 limit , url-loader 會調用 file-loader 進行處理,參數也會直接傳給 file-loader。
也就是說,其實咱們只安裝一個 url-loader 就能夠了。可是爲了之後的操做方便,咱們這裏就順便安裝上 file-loader。
前邊兩節課程,打包後的圖片並無放到images文件夾下,要放到 images 文件夾下,其實只須要配置咱們的 url-loader 選項就能夠了。
module:{
rules: [
{
test: /\.css$/,
use: extractTextPlugin.extract({
fallback: "style-loader",
use: "css-loader"
})
},{
test:/\.(png|jpg|gif)/ ,
use:[{
loader:'url-loader',
options:{
limit:5000,
outputPath:'images/',
}
}]
}
]
},
複製代碼
在處理 css 時咱們已經學會如何使用 extract-text-webpack-plugin 插件提取 css,利用 extract-text-webpack-plugin 插件很輕鬆的就把 CSS 文件分離了出來,可是 CSS 路徑並不正確,不少小夥伴就在這裏搞個幾天仍是沒有頭緒,網上也給出了不少的解決方案,我覺的最好的解決方案是使用publicPath 解決,我也一直在用。
publicPath:是在 webpack.config.js 文件的 output 選項中,主要做用就是處理靜態文件路徑的。
在處理前,咱們在 webpack.config.js 上方聲明一個對象,叫 website。
var website ={
publicPath:"http://192.168.1.108:1717/"
}
複製代碼
注意,這裏的 IP 和端口,是你本機的 ip 或者是你 devServer 配置的 IP 和端口。 而後在 output 選項中引用這個對象的 publicPath 屬性。
//出口文件的配置項
output:{
//輸出的路徑,用了Node語法
path:path.resolve(__dirname,'dist'),
//輸出的文件名稱
filename:'[name].js',
publicPath:website.publicPath
},
複製代碼
配置完成後,你再使用 webpack 命令進行打包,你會發現原來的相對路徑改成了絕對路徑,這樣來說速度更快。
{
test:/\.(png|woff|woff2|svg|ttf|eot)$/,
use:{
loader:'url-loader',
options: {
limit: 100000, //這裏要足夠大這樣全部的字體圖標都會打包到css中
}
}
複製代碼
上文中的 limit 必定要保證大於最大字體文件的大小,由於這個參數是告訴 url-loader,若是文件小於這個參數,那麼就以 Data Url 的方式直接構建到文件中。使用這種方式最方便,不用打包後路徑的問題,可是缺點就是構建出來的文件特別大,若是線上不要使用這種方式打包。
{
test: /\.(woff|woff2|svg|ttf|eot)$/,
use:[
{
loader:'file-loader',
options:{name:'fonts/[name].[hash:8].[ext]'}}
//項目設置打包到dist下的fonts文件夾下
]
}
複製代碼
打包中會遇到的問題就是路徑不對,能夠經過配置 publicPath 解決。
在實際工做中,咱們的項目都會配置一個 Json 的文件或者說 API 文件,做爲項目的配置文件。有時候你也會從後臺讀取到一個 json 的文件,這節課就學習如何在 webpack 環境中使用 Json。若是你會 webpack1 或者 webpack2 版本中,你是須要加載一個 json-loader 的 loader 進來的,可是在webpack3.x 版本中,你再也不須要另外引入了。
讀出 Json 內容 第一步:如今咱們的 index.html 模板中加入一個層,並給層一個 Id,爲了是在 javascript 代碼中能夠方便引用。
<div id="json"></div>
複製代碼
第二步:到 src 文件夾下,找到入口文件,我這裏是 entry.js 文件。修改裏邊的代碼,以下:
var json =require('../config.json');
document.getElementById("json").innerHTML= json.name;
複製代碼
這兩行代碼很是簡單,第一行是引入咱們的 json 文件,第二行駛寫入到到 DOM 中。
html-webpack-plugin 能夠根據你設置的模板,在每次運行後生成對應的模板文件,同時所依賴的 CSS/JS 也都會被引入,若是 CSS/JS 中含有 hash 值,則 html-webpack-plugin 生成的模板文件也會引入正確版本的 CSS/JS 文件。
安裝
npm install html-webpack-plugin --save-dev
複製代碼
引入
在webpack.config.js中引入:
const HtmlWebpackPlugin = require('html-webpack-plugin');
複製代碼
配置
module.exports = {
entry: './app/index.js',
output: {
...
},
module: {
...
},
plugins: [
new HtmlWebpackPlugin({
title: "This is the result",
filename: "./index.html",
template: "./app/index.html",
inject: "body",
favicon: "",
minify: {
caseSensitive: false,
collapseBooleanAttributes: true,
collapseWhitespace: true
},
hash: true,
cache: true,
chunks: ""
})
]
};
複製代碼
而後看一下這些參數的意義:
html-withimg-loader html-withimg-loader 就是咱們今天的重點了,這個插件並非很火,也是我我的喜歡的一個小loader 。解決的問題就是在hmtl文件中引入 標籤的問題。
安裝:
npm install html-withimg-loader --save
複製代碼
配置 loader webpack.config.js
{
test: /\.(htm|html)$/i,
use:[ 'html-withimg-loader']
}
複製代碼
而後在終端中能夠進行打包了。你會發現 images 被很好的打包了。而且路徑也徹底正確。
在使用 webpack-cli 進行打包時,經過命令 webpack --watch便可開啓 watch 模式,進入 watch 模式以後,一旦依賴樹中的某一個模塊發生了變化,webpack 就會從新進行編譯。
在 webpack 中打包生成的文件會覆蓋以前的文件,不過生成文件的時候文件名加了 hash 以後會每次都生成不同的文件,這就會很麻煩,不但會生成不少冗餘的文件,還很難搞清楚究竟是哪一個文件,這就須要引入該插件
npm install –save-dev clean-webpack-plugin
複製代碼
//webpack.config.js
//引入clean-webpack-plugin
const CleanWebpackPlugin = require('clean-webpack-plugin');
//plugin 插入你想刪除的路徑,注意在生成出來文件以前,他會刪除 public 的文件夾,而不是根據生成的文件來刪除對應的文件。
new CleanWebpackPlugin(['public']);
複製代碼
webpack-dev-server 簡介:
安裝webpack-dev-server
npm install webpack-dev-server --save-dev
複製代碼
在 webpack.config.js 中添加配置
var webpack=require('webpack');
module.exports = {
……
devServer: {
historyApiFallback: true,
inline: true,//注意:不寫hot: true,不然瀏覽器沒法自動更新;也不要寫 colors:true,progress:true等,webpack2.x已不支持這些
},
plugins:[
……
new webpack.HotModuleReplacementPlugin()
]
……
};
複製代碼
在 package.json 裏配置運行的命令
"scripts":
{
  "start": "webpack-dev-server --inline"
},
複製代碼
若是你有單獨的後端開發服務器 API,而且但願在同域名下發送 API 請求 ,那麼代理某些 URL 會頗有用。 webpack-dev-server 使用了很是強大的 http-proxy-middleware 包。
配置以下:
proxy: {
'/apis': {
target: '', //要代理到的地址
secure: false, //若地址爲https,須要設置爲false
onProxyReq: function(proxyReq, req, res) { //提早設置一些代理的頭部,如token信息等
},
//...其餘配置請自行查閱文檔http-proxy-middleware文檔
}
}
複製代碼
DevServer 還支持一 種叫作模塊熱替換( Hot Module Replacement )的技術可在不刷新整個網頁的狀況下 作到超 靈敏實時預覽。原理是在一個源碼發生變化時,只需從新編譯發生變化的模塊,再用新輸 出 的模塊替換掉瀏覽器中對應的老模塊 。
模塊熱替換技術的優點以下:
總的來講,模塊熱替換技術在很大程度上提高了開發效率和體驗 。
DevServer 默認不會開啓模塊熱替換模式,要開啓該模式,則只 需在啓動時帶上參數 --hot ,完整的命令是 webpack-dev-server --hot。
除了經過在啓動時帶上 --hot 參數,還能夠經過接入 Plugin 實現,相關代碼以下 :
canst HotModuleReplacementPlugin = require (’ webpack/lib/HotModuleReplacementPlugin ’);
module.exports = {
entry:{
//爲每一個入口都注入代理客戶端
main: [’ webpack-dev-server/client?http://localhost:8080 /’, ’webpack/hot/dev-server ’,’. / src/main.j s ’],
},
plugIns : [
//該插件的做用就是實現模塊熱替換,實際上若啓動時帶上 、 --hot 、參數,就會注入該插件,生 成 .hot-update.json 文件。
new HotModuleReplacementPlugin() ,
],
devServer : {
//告訴 DevServer 要開啓 模塊熱替換模式
hot: true ,
},
};
複製代碼
藉助於 style-loader 的幫助,CSS 的模塊熱替換其實是至關簡單的。當更新 CSS 依賴模塊時,此 loader 在後臺使用 module.hot.accept 來修補(patch) <style>
標籤。
但當修改 js 文件時,咱們會發現模塊熱替換沒有生效,而是整個頁面被刷新了,爲了讓使用者在使用模塊熱替換功能時能靈活地控制老模塊被替換時的邏輯,webpack 容許在源碼中定義一些代碼去作相應的處理。
// 只有當開啓了模塊熱替換時 module.hot 才存在
if (module.hot) {
module.hot.accept(['.IAppComponent'],()=>{
//在新的 AppComponent 加載成功後從新執行組建渲染邏輯 render(<AppComponentl>, window.document.getElementByid ('app'));
}) ;
}
複製代碼
其中的 module.hot 是當開啓模塊熱替換後注入全局的 API,用於控制模塊熱替換的邏輯 。 當子模塊發生更新時,更新事件會一層層地向上傳遞,也就是從 AppComponent.js 文件傳遞到 main.js 文件,直到有某層的文件接收了當前變化的模塊,即 main.js 文 件中定義的 module.hot.accept(['.IAppComponent'], callback),這時就會調用 callback 函數去執行自定義邏輯。 若是事件一直往上拋,到最外層都沒有文件接收它,則會直接刷新網頁。
做爲一個程序員天天的大部分工做就是調試本身寫的程序,那咱們使用了webpack後,因此代碼都打包到了一塊兒,給調試帶來了麻煩,可是webpack已經爲咱們充分考慮好了這點,它支持生產 Source Maps 來方便咱們的調試。 在使用 webpack 時只要經過簡單的 devtool 配置,webapck 就會自動給咱們生產 source maps 文件,map 文件是一種對應編譯文件和源文件的方法,讓咱們調試起來更簡單。
在配置 devtool 時,webpack 給咱們提供了四種選項:
我的意見是,若是大型項目可使用 source-map,若是是中小型項目使用 eval-source-map 就徹底能夠應對,須要強調說明的是,source map 只適用於開發階段,上線前記得修改這些調試設置。
簡單的配置:
module.exports = {
devtool: 'eval-source-map',
entry: __dirname + "/app/main.js",
output: {
path: __dirname + "/public",
filename: "bundle.js"
}
}
複製代碼
首先,要使 webpack 支持 eslint,就要要安裝 eslint-loader ,命令以下:
npm install --save-dev eslint-loader
複製代碼
在 webpack.config.js 中添加以下代碼:
{
test: /\.js$/,
loader: 'eslint-loader',
enforce: "pre",
include: [path.resolve(__dirname, 'src')], // 指定檢查的目錄
options: { // 這裏的配置項參數將會被傳遞到 eslint 的 CLIEngine
formatter: require('eslint-friendly-formatter') // 指定錯誤報告的格式規範
}
}
複製代碼
注:formatter 默認是 stylish ,若是想用第三方的能夠安裝該插件,如上方的示例中的 eslint-friendly-formatter 。
其次,要想 webpack 具備 eslint 的能力,就要安裝 eslint,命令以下:
npm install --save-dev eslint
複製代碼
最後,項目想要使用那些 eslin 規則,能夠建立一個配置項文件 '.eslintrc.js',代碼以下:
module.exports = {
root: true,
parserOptions: {
sourceType: 'module'
},
env: {
browser: true,
},
rules: {
"indent": ["error", 2],
"quotes": ["error", "double"],
"semi": ["error", "always"],
"no-console": "error",
"arrow-parens": 0
}
}
複製代碼
這樣,一個簡單的 webpack 引入 eslint 已經完成了。
webpack 確實是一個功能強大的模塊打包工具,豐富的 loader 和 plugin 使得其功能多而強。學習 webpack 使得咱們能夠自定義本身的開發環境,無需依賴 create-react-app 和 Vue-Cli 這類腳手架,也能夠針對不一樣的需求對代碼進行不一樣方案的處理。