初始化項目
- 進入一個文件夾作為項目的根目錄
- npm init
- 新建src, dist目錄,package.json,webpack.config.js文件
- 安裝項目需要的依賴
cnpm i webpack@4.1.0 webpack-cli@2.0.14 style-loader@0.21.0 css-loader@0.28.11 webpack-dev-server@3.1.3 -D
- 配置package.json里面的script命令
...
"scripts": {
"test": "webpack --mode development",//打包測試環境代碼
"build": "webpack --mode production" //打包開發生成環境代碼
},
...
- npm run dev 啟動項目
webpack配置文件
- webpack啟動的時候,默認加載與package.json同級的webpack.config.js文件
- webpack詳細的配置文件,里面涉及到的loader,插件;后面會詳細的講到
module.exports = {
entry:"./src/index.js" ,
output: {
path:path.join(__dirname, 'dist'),
filename:'[name].[hash:8].js'
},
module: {
rules: [
{
test: /\.css$/,
loader: ['style-loader','css-loader']
},
{
test: /\.(png|jpg|gif|svg|bmp)/,
use: {
loader: 'url-loader',
options:{
limit:9*1024,
//指定拷貝文件的輸出目錄
outputPath: 'images/'
}
}
},
{
test: /\.(html|htm)/,
loader:'html-withimg-loader'
},
{
test: /\.less$/,
use: ['style-loader','css-loader', 'less-loader']
},
{
test: /\.scss/,
use: ['style-loader','css-loader', 'sass-loader']
}
]
},
plugins: [
new CleanWebpackPlugin([path.join(__dirname, 'dist')]),
new HtmlWebpackPlugin({
template: './src/index.html',
filename: 'index.html',
title: 'webpack',
hash: true,
chunks:['vendor', 'index'],
}),
],
devServer: {
contentBase: './dist',
host: 'localhost',
port:8080,
//服務器返回給瀏覽器的時候是否啟用gzip壓縮
compress: true,
}
}
ps:
- entry值有三種情況
- 值為字符串,當值為字符串的時候,webpack會以該文件為入口文件分析其依賴文件,并且打包成chunk, 作為資源asset輸出,默認為main.js
entry: './src/index.js',
entry: ['./src/index.js', './src/base.js']
- 值為對象, 當值為對象的時候,就是我們常說的多入口項目;webpack會先找到每個入口,從各個入口分別出發,找到每個入口依賴的模塊,然后生成chunk(代碼塊), 每個chunk名稱就是entry對應的key,將chunk寫到文件系統中
entry: { index: './src/index.js', base: './src/base.js', vendor: 'jquery' },
- 值為字符串,當值為字符串的時候,webpack會以該文件為入口文件分析其依賴文件,并且打包成chunk, 作為資源asset輸出,默認為main.js
- output 對象
- path: 輸出的文件路徑,必須是絕對路徑
path:path.join(__dirname, 'dist')
- filename: 打包后的文件名,name名字如果是單入口默認是main, 如果是多入口是對應的Key名稱;hash是根據打包之后的內容算出來的hash值,內容發生變化,hash也會變化,長度默認是20位,可以通過[hash:8]取出前8位
filename:'[name].[hash:8].js'
- path: 輸出的文件路徑,必須是絕對路徑
- module 對象
- rules 是一個數組, 用來自定義匹配的文件用什么Loader去處理
[{ test: /\.css$/, loader: ['style-loader','css-loader'] }]
- plugins 數組, 放入多個插件實例,用來處理相關邏輯
new CleanWebpackPlugin([path.join(__dirname, 'dist')]),打包前刪除dist目錄
- devServer對象
{
open:true,//自動打開瀏覽器
inline:true,//在打包后文件里注入一個websocket客戶端,監聽文件變化,自動刷新
contentBase: './dist', //指定靜態文件的根目錄
host: 'localhost',//配置主機
port:8080, //配置端口號,默認8080
compress: true, //服務器返回給瀏覽器的時候是否啟用gzip壓縮
}
配置webpack靜態服務器
- 安裝webpack-dev-server
cnpm i webpack-dev-server@3.1.3 -D
- webpack.config.js文件配置
"scripts": {
//開啟一個靜態服務器
//--open 自動打開瀏覽器
"dev": "webpack-dev-server --mode development "
},
- package.json文件需要添加的配置
//配置靜態服務器,可以預覽打包后的項目
devServer: {
open:true,//自動打開瀏覽器
//靜態文件的根目錄
contentBase: './dist',
//配置主機
host: 'localhost',
//配置端口號,默認8080
port:8080,
//服務器返回給瀏覽器的時候是否啟用gzip壓縮
compress: true,
}
- webpack-dev-server 打包后的文件是放在內存里面的,所以在硬盤里面是找不到這樣的文件的
自動生成html文件
- 安裝html-webpack-plugin插件
cnpm i html-webpack-plugin@3.2.0
- src目錄下面添加一個模板文件index.html
- 配置package.json文件
plugins: [
//此插件可以自動產出html文件,默認保存在output.path指定的目錄下面
new HtmlWebpackPlugin({
//指定產出的html模板
template: './src/index.html',
//產出的html文件名稱
filename: 'index.html',
//可以往模板里面傳入自定義標題,模板默認使用ejs語法;所以可以在模板里面使用ejs語法,寫入變量
title: 'webpack',
//會在插入的靜態文件加上查詢字符串,避免瀏覽器的緩存;如果是單入口文件的話,每次打包之后都是main.js,瀏覽器會有緩存,如果想去掉這個緩存的話,就可以加入查詢字符串,避免瀏覽器的緩存
hash: true,
//在產出的html文件里面引入哪些代碼塊,里面的名字要跟entry里面key對應
chunks:['index']
})
],
- 可以在模板文件里面以ejs語法寫入傳進去的變量
...
<head>
<meta charset="UTF-8">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
...
- 生成的目標html文件
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>webpack</title>
</head>
<body>
<script type="text/javascript" src="main.ef8b627c.js?ef8b627c68ea6ee379e1"></script></body>
</html>
構建的時候自動刪除dist目錄
- 安裝clean-webpack-plugin插件
cnpm i install clean-webpack-plugin@0.1.19 -D
- 配置webpack.config.js插件
plugins: [
//刪除dist目錄
new CleanWebpackPlugin([path.join(__dirname, 'dist')]),
]
引入第三方模塊,以jquery為例
- 如果兩個模塊分別依賴jquery,分別單獨引入的話,會導致每個chunk都特別大
項目中圖片的使用(css, js,html分別引入)
-
css中引入圖片
- 安裝file-loader,url-loader
cnpm i install file-loader@1.1.11 url-loader@1.0.1 -D
- 配置webpack.config.js
{ test: /\.(png|jpg|gif|svg|bmp)/, use: { loader: 'url-loader', options:{ //9kb以內的圖片處理成base64字符串 limit:9*1024, //指定拷貝文件的輸出目錄 outputPath: 'images/' } } }
- file-loader和url-loader兩者關系: url-loader可以在文件比較小的時候,直接變成base64字符串內嵌到css文件里面,如果文件超過指定的限制,會調用file-loader; file-loader是解析圖片地址,把圖片從源位置拷貝到目標位置并修改源引用地址,可以處理任意的二進制數據,比如bootstrap里面的字體
- 安裝file-loader,url-loader
-
js中引入圖片
- webpack.config.js配置與上面css中引入配置一樣
- 使用
//會返回一個打包后的地址 let src = require('./images/thumb.png'); let img = new Image(); img.src = src; document.body.appendChild(img);
-
html 中引入圖片
- 安裝html-withimg-loader
cnpm i html-withimg-loader -D
- 配置webpack.config.js
{ test: /\.(html|htm)/, loader:'html-withimg-loader' }
- 此插件可以將引入的源文件地址,更改為打包后的圖片地址
- 安裝html-withimg-loader
項目中less,sass的使用
- 安裝less, less-loader, node-sass sass-loader
cnpm i less@3.0.4 less-loader@4.1.0 node-sass@4.9.0 sass-loader@7.0.1 -D
- 配置webpack.config.js
{
test: /\.less$/,
use: ['style-loader','css-loader', 'less-loader']
},
{
test: /\.scss/,
use: ['style-loader','css-loader', 'sass-loader']
}
提取css文件
- 安裝extract-text-webpack-plugin插件
cnpm i extract-text-webpack-plugin@next -D
- 配置webpack.config.js
```
//實例插件
let cssExtract = new ExtractTextWebpackPlugin('css/css.css');//
傳入的是打包后的文件名
let lessExtract = new ExtractTextWebpackPlugin('css/less.css');
let scssExtract = new ExtractTextWebpackPlugin('css/scss.css');
...
//配置loader
module: {
rules: [
{
test: /\.css$/,
use: cssExtract.extract({
use: ['css-loader']
})
},
{
test: /\.less$/,
use: lessExtract.extract({
use: ['css-loader', 'less-loader']
})
},
{
test: /\.scss/,
use: scssExtract.extract({
use: ['css-loader', 'sass-loader']
})
}
]
},
...
//配置插件
plugins:[
cssExtract,
lessExtract,
scssExtract
]
```
給css3添加前綴
- 安裝postcss-loader autoprefix
cnpm i postcss-loader autoprefixer -D
- 配置webpack.config.js
{
test: /\.less$/,
use: lessExtract.extract({
//最后面添加一個postcss-loader
use: ['css-loader', 'less-loader', 'postcss-loader']
})
},
- 在根目錄下面新建postcss.config.js,里面的內容如下
module.exports = {
plugins: [require('autoprefixer')]
}
壓縮css
- 配置webpack.config.js,給css-loader添加minimize參數
module:{
rules:[
{
test: /\.less$/,
use: lessExtract.extract({
use: ['css-loader?minimize', 'less-loader', 'postcss-loader']
})
},
]
}
解析es6,es7代碼
- 安裝相關插件
cnpm i babel-loader@7.1.4 babel-core@6.26.3 babel-preset-env@1.6.1 babel-preset-stage-0@6.24.1 babel-preset-react@6.24.1 -D
- 配置webpack.config.js
{
test: /\.jsx?$/,
use:[
{
loader: 'babel-loader'
}
],
//只轉換或者編譯src目錄下面的文件
include: path.join(__dirname,'./src'),
//不用解析node_modules
exclude:/node_modules/
},
- 在package.json同級目錄下面放入.babelrc文件,內容如下
{
"presets":["env", "stage-0"]
}
壓縮js代碼
- 安裝uglifyjs-webpack-plugin插件
cnpm i uglifyjs-webpack-plugin -D
- 配置webpack.config.js
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
...
plugins: [
new UglifyjsWebpackPlugin(),
]
webpack.config.js配置devtool定位源碼錯誤位置
-
devtool:'source-map'
,//生成單獨map文件,可以定位到哪一列出錯了,定位的錯誤最準確, 但是體積最大 -
devtool:'cheap-module-source-map'
,//生成單獨文件,體積更小,但只能定位到哪一行出錯了 -
devtool:'eval-source-map'
,//不會生成單獨文件,在文件的底部生成map文件進行映射,比較大 -
devtool:'cheap-module-eval-source-map'
,//不會生成單獨文件,只能定位到行
拷貝文件
- 安裝copy-webpack-plugin 插件
cnpm i copy-webpack-plugin -D
- 配置webpack.config.js
const CopyWebpackPlugin = require('copy-webpack-plugin');
...
plugins:[
new CopyWebpackPlugin([{
from:path.join(__dirname, 'src/public'),//源文件目錄
to:path.join(__dirname, 'dist/public')//目標目錄
}])
]
項目中引入bootstrap
- 安裝bootstrap
cnpm i bootstrap -D
- 配置webpack.config.js
resolve:{
//配置擴展名, 引入模塊的時候可以不加擴展名,加載順序從前往后
extensions:[".js", ".json"],
//配置別名,在js里面直接引用
alias:{
"bootstrap":"bootstrap/dist/css/bootstrap.css"
}
},
module:{
rules:[
{
//匹配的文件添加eot|woff|woff2|ttf用來處理字體文件
test: /\.(png|jpg|gif|svg|bmp|eot|woff|woff2|ttf)/,
use: {
loader: 'url-loader',
options:{
limit:9*1024,
outputPath: 'images/'
}
}
},
]
}
- 項目中加載bootstrap
require('bootstrap')
- 引入的bootstrap因為加載的是以css為擴展,所以最終會被以下loader處理,文件最終會被提取到css.css文件里面,放到目標目錄
{
test: /\.css$/,
use: cssExtract.extract({
use: ['css-loader?minimize']
})
},
項目中使用react
- 安裝react react-dom babel-core babel-loader
cnpm i react react-dom babel-core babel-loader -D
- 配置webpack.config.js
{
test: /\.jsx?$/,
use:[
{
loader: 'babel-loader'
}
]
},
- 在與package.json同級目錄新建.babelrc文件,內容如下
{
"presets":["env", "stage-0", "react"]
}
- 項目中使用
import React from 'react';
import ReactDom from 'react-dom';
ReactDom.render(<h1>hello world</h1>, document.getElementById('root'));
(優化)配置resolve減少模塊搜索范圍
- 配置如下
resolve:{
//加載模塊的時候,會到下面指定的目錄查找
modules:['node_modules','./libs'],
//當加載的模塊是包的時候,找的入口文件是package.josn里面的main字段,如果沒有會依次找browser,node字段; 主要是用來加載同一個api,但是區分不同的平臺
mainFields:['main', 'browser', 'node'],
//配置擴展名, 引入模塊的時候可以不加擴展名,加載順序從前往后
extensions:[".js", ".json"],
//配置別名,在js里面直接引用
alias:{
//當加載第三方模塊的時候,直接加載編譯過后的代碼,不會再去區分環境加載不同的代碼
react:'react/cjs/react.production.min.js',
"bootstrap":"bootstrap/dist/css/bootstrap.css",
vue: 'vue/dist/vue.js'
}
},
(優化)module.noParse
- 配置此參數,模塊中如果依賴了某第三方某塊,webpack不會對此模塊再進行依賴分析,提高了依賴分析速度
- 配置webpack.config.js
...
module:{
noParse:[/react\.production\.min\.js/]
}
...
DLL 動態鏈接庫
創建動態鏈接庫用來包含其他模塊要用到的函數和數據
-
創建分兩步
- 把基礎模塊獨立出來打包到單獨的動態鏈接庫里面
- 當需要導入的模塊在動態鏈接庫里面的時候,模塊不能再次被打包,而是去動態鏈接庫里面去獲取dll-plugin
-
安裝DLLPlugin, DLLReferencePlugin插件,這兩個插件是webpack自帶的
- DLLPlugin插件: 用于打包出一個動態鏈接庫
- DLLReferencePlugin:在配置文件中引入DLLPlugin插件打包好的動態鏈接庫
-
具體的配置
- 創建打包dll的webpack.dll.config.js, 具體內容如下
const path = require('path'); const webpack = require('webpack'); /** * target: '_dll_[name]' 指定的是導出變量的名稱 * */ module.exports = { entry: { //將react, react-dom打包到dll文件里面 react: ['react', 'react-dom'], }, output: { path: path.join(__dirname, 'dist'),//dll文件輸出路勁 filename: '[name]_dll.js',//輸出動態鏈接庫的名字,name取的是enter里面的react名稱 library: '_dll_[name]',//暴露成全局變量的名字,放到window上面;其他模塊會從此變量上說去到里面的模塊 //這個屬性和library是配合來使用的;默認值是var,就是將library對應的名稱通過var聲明(var _dll_[name]=...) libraryTarget: 'var' }, plugins:[ new webpack.DllPlugin({ name: '_dll_[name]',//這個名字一定要與output.target保持一致,是在manifest文件中使用的 path: path.join(__dirname, 'dist', 'manifest.json')//會生成一個描述文件,里面有個name屬性,就是上面指定的 }) ] }
- 配置package.json里面的打包腳本
... "scripts": { "build:react": "webpack --config webpack.react.config.js --mode production" }, ...
- 配置webpack.config.js
const webpack = require('webpack'); ... plugins: [ //這個插件指的是想在這個配置文件里面引入另一個動態鏈接庫,會找到mainfest指定的文件,通過這個文件找到里面的name屬性對應的動態鏈接庫,當加載庫的時候,會到這個動態鏈接庫里面看有沒有,有的話直接加載,不需要編譯,加快了編譯速度; // 當在業務模塊里面引入react的時候,會先找manifest.json文件,看看里面有沒有react模塊,有的話,會找到對應的name屬性(_dll_[name]),然后找到window._dll_[name]模塊,從而拿到里面的編譯過后的react(之所以能拿到是因為動態鏈接庫編譯了react,并且放到了window._dll_[name]屬性上了),所以不用再次打包react,減少了編譯的時間 new webpack.DllReferencePlugin({ manifest: path.join(__dirname, 'dist', 'manifest.json') }), ]
區分正式測試環境
cdn設置
- HTML文件不緩存,放在自己的服務器上,關閉自己服務器的緩存,靜態資源的URL變成指向CDN服務器的地址
- 靜態的JavaScript、CSS、圖片等文件開啟CDN和緩存,并且文件名帶上HASH值
- 為了并行加載不阻塞,把不同的靜態資源分配到不同的CDN服務器上
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name]_[hash:8].js',
publicPath: 'http://baidu.com'//生成的靜態資源的前綴
}
package.json設置腳本運行的環境變量
- window系統設置是通過
"test": "set NDOE_ENV=development && webpack --mode development"
- mac系統設置是通過
"test": "export NDOE_ENV=development && webpack --mode development"
3.如果統一的話 安裝包cnpm install cross-env -D
并且通過以下方式設置環境變量
"test": "crocess_env NDOE_ENV=development && webpack --mode development"
ps: 以上設置的環境變量在webpack的配置文件中是可以直接通過process.env.NODE_ENV
來獲取到的
源代碼中區分測試還是正式環境
- 配置plugin
...
plugins:[
new webpack.DefinePlugin({
'process.env':{
NODE_ENV: JSON.stringify(process.env.NODE_ENV),
PRODUCT_NAEM: JSON.stringify(process.env.PRODUCT_NAME)
}
})
]
...
- 源碼中使用方式
if(process.env.NODE_ENV === 'development') {
}
模塊熱替換
- 模塊熱替換(hot module replacement)的技術可以在不刷新整個網頁的情況下只更新指定的模塊,原理是當一個源碼發生變化時,只重新編譯發生變化的模塊,再用新輸出的模塊替換掉瀏覽器中老的模塊
- 編譯更快,需要的時間更短
- 不刷新網頁可以保留網頁運行狀態
- webpack.config.js配置
const webpack = require('webpack')
...
devServer: {
hot: true
}
plugins:[
...
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin()
]
- 源碼中使用方式
if(module.hot) {
//如果檢測到了base更新了,則執行這個函數
module.hot.accept('./base', function() { //如果這個文件發生變化了,則執行這個函數,不會刷新頁面了
})
}
Tree Shaking
ps: 可以剔除js中不用的代碼;依賴靜態的es6模塊化語法,比如通過import 和export 導入導出
- 比如引入一個公共庫,里面有兩個方法,引入到業務代碼里面,只用了其中的一個,用這種方式,可以把沒有用到的死代碼剔除掉,減少包的大小
- 沒有使用Tree-Shaking之前
//info.js
export function getName() {
return 'tree-shaking'
}
export function getAge() {
return '2.0'
}
//index.js
import { getName } from './info.js'
let name = getName;
console.log(name);
//main.b78eb869.js 打包之后的代碼
(function(module, exports, __webpack_require__) {
"use strict";
eval("\n\nObject.defineProperty(exports, \"__esModule\", {\n value: true\n});\nexports.getName = getName;\nexports.getAge = getAge;\n\nfunction getName() {\n return 'tree-shaking';\n}\n\nfunction getAge() {\n return '2.0';\n}\n\n//# sourceURL=webpack:///./src1/info.js?");
/***/
ps: index.js并沒有使用getAge方法,但是在打包之后的代碼里面還是有getAge這個方法的
- 我們希望的是沒用用到的代碼剔除
- webpack.config.js配置如下
module:{
rules:[
{
test: /\.js$/,
use:{
loader:'babel-loader',
options:{
presets:[
//env模塊編譯es6語法,但是不編譯module語法
["env", {modules:false}]//含義是關閉 Babel 的模塊轉換功能,保留原本的 ES6 模塊化語法
]
}
},
include:path.resolve('./src'),//轉義該目錄下的文件
exclude:/node_modules/ //該目錄下的文件轉成es5
}
]
},
- 再次編譯,會發現getName已經不見了
提取公共代碼
- 為什么要提取公共代碼呢
- 相同的資源被重復的加載,浪費用戶的流量和服務器的成本;
- 每個頁面需要加載的資源太大,導致網頁首屏加載緩慢,影響用戶體驗。如果能把公共代碼抽離成單獨文件進行加載能進行優化,可以減少網絡傳輸流量,降低服務器成本
- 如何提取
- 基礎類庫,長期緩存
- 頁面之間的公用代碼
- 各個頁面單獨生成文件
es6 里面class屬性的轉換
- 安裝插件
npm install --save-dev babel-plugin-transform-class-properties
2.配置.babelrc
// without options
{
"plugins": ["transform-class-properties"]
}
// with options
{
"plugins": [
["transform-class-properties", { "spec": true }]
]
}
webpack概念
-
功能
- 代碼轉換:TypeScript 編譯成 JavaScript、SCSS 編譯成 CSS 等
- 文件優化:壓縮 JavaScript、CSS、HTML 代碼,壓縮合并圖片等。
- 代碼分割:提取多個頁面的公共代碼、提取首屏不需要執行部分的代碼讓其異步加載。
- 模塊合并:在采用模塊化的項目里會有很多個模塊和文件,需要構建功能把模塊分類合并成一個文件。
- 自動刷新:監聽本地源代碼的變化,自動重新構建、刷新瀏覽器。
- 代碼校驗:在代碼被提交到倉庫前需要校驗代碼是否符合規范,以及單元測試是否通過。
- 自動發布:更新完代碼后,自動構建出線上發布代碼并傳輸給發布系統。
執行原理
Webpack 啟動后會從Entry里配置的Module開始遞歸解析 Entry 依賴的所有 Module。 每找到一個 Module, 就會根據配置的Loader去找出對應的轉換規則,對 Module 進行轉換后,再解析出當前 Module 依賴的 Module。 這些模塊會以 Entry 為單位進行分組,一個 Entry 和其所有依賴的 Module 被分到一個組也就是一個 Chunk。最后 Webpack 會把所有 Chunk 轉換成文件輸出。 在整個流程中 Webpack 會在恰當的時機執行 Plugin 里定義的邏輯。