webpack4.0快速上手
webpack常用來處理前端工程化問題:包括自動化打包、es6轉換、圖片處理、css樣式壓縮等
文章目錄
一、基礎篇
安裝webpack
兩種方式:全局和本地
全局安裝
npm intall -g webpack webpack-cli
// or
yarn global add webpack webpack-cli
使用:
使用webpack對文件進行編譯,如當前目錄存在index.js文件:
webpack index.js
// or
webpack index.js --mode development
// or
webpack index.js --mode production
注意:webpack默認查找路徑爲項目根路徑下的src
本地安裝
全局安裝模式下,當項目放到其他環境時無法通過npm install安裝項目所需依賴,因爲依賴在全局環境中,沒有寫入package.json文件中
本地安裝解決以上問題
// 初始化package.json
npm init -y
// 安裝webpack
yarn add -D webpack webpack-cli
修改package.json文件:
"scripts":{
"build":"webpack"
}
項目目錄:web01/src/index.js
啓動打包
yarn build
// or
npm run build
注意:此時會有提示沒有設置mode,可以在package.json中設置,也可以單獨配置(配置文件中的mode字段)
"scripts":{
"build":"webpack --mode production"
}
配置webpack
需求引入:不光是main.js,如果需要連同html一起生成,則必須通過webpack的配置纔行
需要生成你html文件,必須安裝htmlwebpackplugin插件
npm install -D html-webpack-plugin
然後配置:web01/webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports={
entry: "./src/index.js",/* 以配置文件開始 */
plugins:[
new HtmlWebpackPlugin()
]
}
然後打包,即可看到index.html
一個完整的webpack.config.js包括:
const path = require('path');
module.exports = {
mode: "production", // "production" | "development" | "none" // Chosen mode tells webpack to use its built-in optimizations accordingly.
entry: "./app/entry", // string | object | array // 這裏應用程序開始執行
// webpack 開始打包
output: {
// webpack 如何輸出結果的相關選項
path: path.resolve(__dirname, "dist"), // string
// 所有輸出文件的目標路徑
// 必須是絕對路徑(使用 Node.js 的 path 模塊)
filename: "bundle.js", // string // 「入口分塊(entry chunk)」的文件名模板(出口分塊?)
publicPath: "/assets/", // string // 輸出解析文件的目錄,url 相對於 HTML 頁面
library: "MyLibrary", // string,
// 導出庫(exported library)的名稱
libraryTarget: "umd", // 通用模塊定義 // 導出庫(exported library)的類型
/* 高級輸出配置(點擊顯示) */ },
module: {
// 關於模塊配置
rules: [
// 模塊規則(配置 loader、解析器等選項)
{
test: /\.jsx?$/,
include: [
path.resolve(__dirname, "app")
],
exclude: [
path.resolve(__dirname, "app/demo-files")
],
// 這裏是匹配條件,每個選項都接收一個正則表達式或字符串
// test 和 include 具有相同的作用,都是必須匹配選項
// exclude 是必不匹配選項(優先於 test 和 include)
// 最佳實踐:
// - 只在 test 和 文件名匹配 中使用正則表達式
// - 在 include 和 exclude 中使用絕對路徑數組
// - 儘量避免 exclude,更傾向於使用 include
issuer: { test, include, exclude },
// issuer 條件(導入源)
enforce: "pre",
enforce: "post",
// 標識應用這些規則,即使規則覆蓋(高級選項)
loader: "babel-loader",
// 應該應用的 loader,它相對上下文解析
// 爲了更清晰,`-loader` 後綴在 webpack 2 中不再是可選的
// 查看 webpack 1 升級指南。
options: {
presets: ["es2015"]
},
// loader 的可選項
},
{
test: /\.html$/,
test: "\.html$"
use: [
// 應用多個 loader 和選項
"htmllint-loader",
{
loader: "html-loader",
options: {
/* ... */
}
}
]
},
{ oneOf: [ /* rules */ ] },
// 只使用這些嵌套規則之一
{ rules: [ /* rules */ ] },
// 使用所有這些嵌套規則(合併可用條件)
{ resource: { and: [ /* 條件 */ ] } },
// 僅當所有條件都匹配時才匹配
{ resource: { or: [ /* 條件 */ ] } },
{ resource: [ /* 條件 */ ] },
// 任意條件匹配時匹配(默認爲數組)
{ resource: { not: /* 條件 */ } }
// 條件不匹配時匹配
],
/* 高級模塊配置(點擊展示) */ },
resolve: {
// 解析模塊請求的選項
// (不適用於對 loader 解析)
modules: [
"node_modules",
path.resolve(__dirname, "app")
],
// 用於查找模塊的目錄
extensions: [".js", ".json", ".jsx", ".css"],
// 使用的擴展名
alias: {
// 模塊別名列表
"module": "new-module",
// 起別名:"module" -> "new-module" 和 "module/path/file" -> "new-module/path/file"
"only-module$": "new-module",
// 起別名 "only-module" -> "new-module",但不匹配 "only-module/path/file" -> "new-module/path/file"
"module": path.resolve(__dirname, "app/third/module.js"),
// 起別名 "module" -> "./app/third/module.js" 和 "module/file" 會導致錯誤
// 模塊別名相對於當前上下文導入
},
/* 可供選擇的別名語法(點擊展示) */
/* 高級解析選項(點擊展示) */ },
performance: {
hints: "warning", // 枚舉 maxAssetSize: 200000, // 整數類型(以字節爲單位)
maxEntrypointSize: 400000, // 整數類型(以字節爲單位)
assetFilter: function(assetFilename) {
// 提供資源文件名的斷言函數
return assetFilename.endsWith('.css') || assetFilename.endsWith('.js');
}
},
devtool: "source-map", // enum // 通過在瀏覽器調試工具(browser devtools)中添加元信息(meta info)增強調試
// 犧牲了構建速度的 `source-map' 是最詳細的。
context: __dirname, // string(絕對路徑!)
// webpack 的主目錄
// entry 和 module.rules.loader 選項
// 相對於此目錄解析
target: "web", // 枚舉 // 包(bundle)應該運行的環境
// 更改 塊加載行爲(chunk loading behavior) 和 可用模塊(available module)
externals: ["react", /^@angular\//], // 不要遵循/打包這些模塊,而是在運行時從環境中請求他們
stats: "errors-only", // 精確控制要顯示的 bundle 信息
devServer: {
proxy: { // proxy URLs to backend development server
'/api': 'http://localhost:3000'
},
contentBase: path.join(__dirname, 'public'), // boolean | string | array, static file location
compress: true, // enable gzip compression
historyApiFallback: true, // true for index.html upon 404, object for multiple paths
hot: true, // hot module replacement. Depends on HotModuleReplacementPlugin
https: false, // true for self-signed, object for cert authority
noInfo: true, // only errors & warns on hot reload
// ...
},
plugins: [
// ...
],
// 附加插件列表
/* 高級配置(點擊展示) */}
https://www.webpackjs.com/configuration/
Entry
入口文件,可以是單一入口,和多入口:string,array,object
多入口可以拆分代碼將每個頁面需要的js分離出來,使用對象作爲entry時可以規定輸出文件的名字!
// 可以是如下值
entry: "./app/entry",
entry: ["./app/entry1", "./app/entry2"],
entry: {
a: "./app/entry-a",
b: ["./app/entry-b1", "./app/entry-b2"]
},
Output
指定輸出文件的名字和其他屬性
output: {
// webpack 如何輸出結果的相關選項
path: path.resolve(__dirname, "dist"), // string
// 所有輸出文件的目標路徑
// 必須是絕對路徑(使用 Node.js 的 path 模塊)
filename: "bundle.js", // string
filename: "[name]xxx.js", // 用於多個入口點(entry point)(按照entry定義的名字輸出)
filename: "[chunkhash].js", // 用於長效緩存
// 「入口分塊(entry chunk)」的文件名模板(出口分塊?)
publicPath: "/assets/", // string
publicPath: "",
publicPath: "https://cdn.example.com/",
// 輸出解析文件的目錄,url 相對於 HTML 頁面
library: "MyLibrary", // string,
// 導出庫(exported library)的名稱
libraryTarget: "umd", // 通用模塊定義,或:
// 導出庫(exported library)的類型
/* 高級輸出配置(點擊顯示) */
},
filename: "[hash:4].js
指定4位哈希碼,可以解決瀏覽器緩存不更新文件的情況,如果改變文件內容(此處hash會將entry中入口的文件組合在一起hash),會導致編譯出來的hash碼不相同,同一次編碼出來的hash相同。
path字段要求必須是絕對路徑,所以可以引入node的path模塊:
const path = require("path")
module.exports = {
entry:{...},
output:{
filename:xxxx,
path:path.join(__dirname,'output')
}
}
此處path.join(__dirname,'output')
相當於將輸出放在根目錄下的output文件夾中
publicPath字段:在開發時有可能使用本地路徑的圖片,而生產環境中可能需要使用cdn加速來獲取圖片,該字段可以在編譯輸出文件時自動替換url
二、進階篇
loader
loader 特性
- loader 支持鏈式傳遞。能夠對資源使用流水線(pipeline)。一組鏈式的 loader 將按照相反的順序執行。loader 鏈中的第一個 loader 返回值給下一個 loader。在最後一個 loader,返回 webpack 所預期的 JavaScript。
- loader 可以是同步的,也可以是異步的。
- loader 運行在 Node.js 中,並且能夠執行任何可能的操作。
- loader 接收查詢參數。用於對 loader 傳遞配置。
- loader 也能夠使用
options
對象進行配置。 - 除了使用
package.json
常見的main
屬性,還可以將普通的 npm 模塊導出爲 loader,做法是在package.json
裏定義一個loader
字段。 - 插件(plugin)可以爲 loader 帶來更多特性。
- loader 能夠產生額外的任意文件。
loader 通過(loader)預處理函數,爲 JavaScript 生態系統提供了更多能力。 用戶現在可以更加靈活地引入細粒度邏輯,例如壓縮、打包、語言翻譯和其他更多。
解析 loader
loader 遵循標準的模塊解析。多數情況下,loader 將從模塊路徑(通常將模塊路徑認爲是 npm install
, node_modules
)解析。
loader 模塊需要導出爲一個函數,並且使用 Node.js 兼容的 JavaScript 編寫。通常使用 npm 進行管理,但是也可以將自定義 loader 作爲應用程序中的文件。按照約定,loader 通常被命名爲 xxx-loader
(例如 json-loader
)。有關詳細信息,請查看 如何編寫 loader?。
使用loader來加載css文件和less文件:
npm install --save-dev css-loader
npm install --save-dev less-loader
webpack.config.js
module.exports = {
module: {
rules: [
{ test: /\.css$/, use: ['style-loader','css-loader'] },
{ test: /\.less$/, use: ['style-loader','css-loader','less-loader']}
]
}
};
loader加載方式
- 配置方式:在web.config.js中指定loader
- 內聯式:在每個import語句中來顯示指定loader
- CLI式:在shell命令中指定
配置
module.rules
允許你在 webpack 配置中指定多個 loader。 這是展示 loader 的一種簡明方式,並且有助於使代碼變得簡潔。同時讓你對各個 loader 有個全局概覽:
module: {
rules: [
{
test: /\.css$/,
use: [
{ loader: 'style-loader' },
{
loader: 'css-loader',
options: {
modules: true
}
}
]
}
]
}
內聯
使用 !
將資源中的 loader 分開。分開的每個部分都相對於當前目錄解析。
import Styles from 'style-loader!css-loader?modules!./styles.css';
通過前置所有規則及使用 !
,可以對應覆蓋到配置中的任意 loader。
選項可以傳遞查詢參數,例如 ?key=value&foo=bar
,或者一個 JSON 對象,例如 ?{"key":"value","foo":"bar"}
。
儘可能使用
module.rules
,因爲這樣可以減少源碼中的代碼量,並且可以在出錯時,更快地調試和定位 loader 中的問題。
CLI
webpack --module-bind jade-loader --module-bind 'css=style-loader!css-loader'
這會對 .jade
文件使用 jade-loader
,對 .css
文件使用 style-loader
和 css-loader
。
plugins
插件用於解決loader無法實現的事情
自定義插件
webpack 插件是一個具有 apply
屬性的 JavaScript 對象。apply
屬性會被 webpack compiler 調用,並且 compiler 對象可在整個編譯生命週期訪問。
ConsoleLogOnBuildWebpackPlugin.js
const pluginName = 'ConsoleLogOnBuildWebpackPlugin';
class ConsoleLogOnBuildWebpackPlugin {
apply(compiler) {
compiler.hooks.run.tap(pluginName, compilation => {
console.log("webpack 構建過程開始!");
});
}
}
compiler hook 的 tap 方法的第一個參數是駝峯式命名的插件名稱。建議爲此使用一個常量,以便它可以在所有 hook 中複用。
使用
通過在webpack.config.js中的plugins中new一個對象出來
const HtmlWebpackPlugin = require('html-webpack-plugin'); //通過 npm 安裝
const ConsoleLogOnBuildWebpackPlugin = require('ConsoleLogOnBuildWebpackPlugin'); //訪問內置的插件
const path = require('path');
const config = {
entry: './path/to/my/entry/file.js',
output: {
filename: 'my-first-webpack.bundle.js',
path: path.resolve(__dirname, 'dist')
},
module: {
rules: [
{
test: /\.(js|jsx)$/,
use: 'babel-loader'
}
]
},
plugins: [
new ConsoleLogOnBuildWebpackPlugin(),
new HtmlWebpackPlugin({template: './src/index.html'})
]
};
module.exports = config;
使用webpack轉換ts代碼
我們可以使用tsc轉換工具來轉換ts代碼到js:
npm install -g tsc
tsc src/index.ts
如果使用webpack來轉換的話:
先安裝ts-loader和typescript
yarn add -D ts-loader typescript
創建ts解析文件:tsconfig.json
{
"compilerOption":{
"sourceMap":true,
}
}
此時yarn build即可生成js文件
熱更新
常規初始化項目,然後在項目中安裝webpack-dev-server
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
+ const webpack = require('webpack');
module.exports = {
entry: {
- app: './src/index.js',
- print: './src/print.js'
+ app: './src/index.js'
},
devtool: 'inline-source-map',
devServer: {
contentBase: './dist',
+ hot: true
},
plugins: [
new CleanWebpackPlugin(['dist']),
new HtmlWebpackPlugin({
title: 'Hot Module Replacement'
}),
+ new webpack.NamedModulesPlugin(),
+ new webpack.HotModuleReplacementPlugin()
],
output: {
filename: '[name].bundle.js',
path: path.resolve(__dirname, 'dist')
}
};
在package.json中
"scripts":{
"build":"webpack",
"dev":"webpack-dev-server"
}
然後:yarn dev
通過Node.js API來熱部署,首先創建:
dev-server.js
const webpackDevServer = require('webpack-dev-server');
const webpack = require('webpack');
const config = require('./webpack.config.js');
const options = {
contentBase: './dist',
hot: true,
host: 'localhost'
};
webpackDevServer.addDevServerEntrypoints(config, options);
const compiler = webpack(config);
const server = new webpackDevServer(compiler, options);
server.listen(5000, 'localhost', () => {
console.log('dev server listening on port 5000');
});
然後:node dev-server.js
在一個項目中同時配置開發與生產環境
在項目根目錄新建兩個配置文件:
- webpack.dev.config.js
- webpack.prod.config.js
在package.json文件中定義兩個腳本命令:
"scripts":{
"dev":"webpack --config webpack.dev.config.js",
"build":"webpack --config webpack.prod.config.js"
}
用npm或者yarn安裝好webpack及其相關包
html-webpack-plugin #生成html文件
css-loader
style-loader
less
less-loader
file-loader # 圖片相關
clean-webpack-plugin # 清理生成目錄
extract-text-webpack-plugin # 導出css文件,注意,如果此處使用該導出工具導出,則在module的rules中需要改變內容:
改變前:
rules:[
{
test:/\.css$/,
loader:['style-loader','css-loader']
},
{
test:/\.(jpg|png|svg)/,
loader:['file-loader']
}
]
改變後:
rules:[
{
test:/\.css$/,
use:ExtractTextWebpackPlugin.extract({
fallback:'style-loader',
use:'css-loader'
})
},
{
test:/\.(jpg|png|svg)/,
loader:['file-loader']
}
]
在每個配置文件中配置一套就可以了
但是,這樣配置會導致很多相同的代碼冗餘,爲了解決該情況,可以使用webpack-merge
webpack-merge
使用方法:
yarn add -D webpack-merge
然後新建一個公有部分的配置文件,這裏比如爲base
webpack.base.config.js
webpack.prod.config.js
webpack.dev.config.js
這樣,將公有部分放在base文件中,其他部分定義獨有的即可
const WebpackMerge=require('webpack-merge')
const baseConfig=require('/webpack.base.config')
module.exports=WebpackMerge(baseConfig,{
// 之前module export部分
})
三、配置React項目
-
安裝webpack、webpack-cli
-
安裝html-webpack-plugin
-
新建src/index.js和src/index.html
-
在index.html中定義根節點
-
安裝react、react-dom
-
安裝babel-loader、@babel/core、@babel/preset-react、@babel/preset-env
-
安裝css-loader、style-loader、less、less-loader
-
配置webpack.config.js
module.exports={ mode:"development", entry:"./src/index.js", module:{ rules:[ { test:/\.(js|jsx)$/, use:[ { loader:"babel-loader", options:{ presets:[ "@babel/preset-react", "@babel/preset-env", ] } } ] }, { test:/\.css$/, use:["style-loader","css-loader"] }, { test:/\.less/, use:["style-loader","css-loader","less-loader"] } ] } }
-
編寫一個react組件開始使用