webpack 能夠看作是模塊打包機:他作的事情是,分析你的項目結構,找到 JavaScript
模塊以及其餘的一些瀏覽器不能直接運行的擴展語言(Scss
、TypeScript
等),將其打包爲合適的格式以供瀏覽器使用css
構建就是把源代碼轉換成發佈到線上可執行的 JavaScript
、CSS、HTML 代碼,包括如下內容:html
TypeScript
編譯成 JavaScript
、SCSS
編譯成 CSS 等等JavaScript
、CSS、HTML 代碼,壓縮合並圖片等構建實際上是工程化、自動化思想在前端開發中的體現。把一系列流程用代碼去實現,讓代碼自動化地執行這一系列複雜的流程。前端
入口(entry point): 指示 webpack 應該使用哪一個模塊,來做爲構建其內部依賴圖的開始,webpack 會找出有哪些模塊和 library 是入口起點(直接和間接)依賴的。vue
./src/index.js
,然而,能夠經過在 webpack 配置中配置 entry 屬性,來指定一個不一樣的入口起點(或者也能夠指定多個入口起點)。出口 output: 屬性告訴 webpack 在哪裏輸出它所建立的 bundles,以及如何命名這些文件,主輸出文件默認爲 ./dist/main.js
,其餘生成文件的默認輸出目錄是 ./dist
node
loader: 讓 webpack 可以去處理那些非 JavaScript 文件(webpack 自身只理解 JavaScript)。loader 能夠將全部類型的文件轉換爲 webpack 可以處理的有效模塊,而後你就能夠利用 webpack 的打包能力,對它們進行處理。react
注意,loader 可以 import 導入任何類型的模塊(例如 .css 文件),這是 webpack 特有的功能,其餘打包程序或任務執行器的可能並不支持。咱們認爲這種語言擴展是有很必要的,由於這可使開發人員建立出更準確的依賴關係圖。webpack
插件 plugins: loader 被用於轉換某些類型的模塊,而插件則能夠用於執行範圍更廣的任務。插件的範圍包括,從打包優化和壓縮,一直到從新定義環境中的變量。插件接口功能極其強大,能夠用來處理各類各樣的任務。ios
模式 mode: 經過選擇 development
或 production
之中的一個,來設置 mode 參數,你能夠啓用相應模式下的 webpack 內置的優化git
咱們在平常的前端開發工做中,通常都會有兩套構建環境:一套開發時使用,一套供線上使用。es6
webpack dev server
和其餘東西UglifyJSPlugin
,sourcemaps
等簡單來講,開發時可能須要打印 debug 信息,包含 sourcemap
文件,而生產環境是用於線上的即代碼都是壓縮後,運行時不打印 debug 信息等。譬如 axios、antd 等咱們的生產環境中須要使用到那麼咱們應該安裝該依賴在生產環境中,而 webpack-dev-server
則是須要安裝在開發環境中
平時咱們 npm
中安裝的文件中有 -S -D, -D 表示咱們的依賴是安裝在開發環境的,而-S 的是安裝依賴在生產環境中。
本文就來帶你搭建基本的前端開發環境,前端開發環境須要什麼呢?
以上配置就能夠知足前端開發中須要的基本配置。下面是本文打包後的效果圖:
mkdir webpack-dev && cd webpack-dev
npm init -y
npm i webpack webpack-cli -D
複製代碼
生成了 package.json 文件,在文件中添加
"scripts": {
"build": "webpack --mode production"
}
複製代碼
--
mode
模式 (必選,否則會有WARNING
),是webpack4
新增的參數選項,默認是production
--mode production
生產環境
uglifyjs-webpack-plugin
代碼壓縮new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("production") })
默認 production
NoEmitOnErrorsPlugin -> optimization.noEmitOnErrors
, 編譯出錯時跳過輸出,以確保輸出資源不包含錯誤ModuleConcatenationPlugin
-> optimization.concatenateModules
, webpack3
添加的做用域提高(Scope Hoisting
)--mode development
開發環境
new webpack.DefinePlugin({ "process.env.NODE_ENV": JSON.stringify("development") })
默認 development
NamedModulesPlugin -> optimization.namedModules
使用模塊熱替換(HMR)時會顯示模塊的相對路徑添加了 scripts 以後,新建src/index.js
,而後執行npm run build
,你就會發現新增了一個 dist
目錄,裏邊存放的是 webpack 構建好的 main.js
文件。
要想對 webpack 中增長更多的配置信息,咱們須要創建一個 webpack 的配置文件。在根目錄下建立 webpack.config.js
後再執行 webpack
命令,webpack 就會使用這個配置文件的配置了
配置中具有如下的基本信息:
module.exports = {
entry: '', // 打包入口:指示 webpack 應該使用哪一個模塊,來做爲構建其內部依賴圖的開始
output: '', // 出口
resolve: {}, // 配置解析:配置別名、extensions 自動解析肯定的擴展等等
devServer: {}, // 開發服務器:run dev/start 的配置,如端口、proxy等
module: {}, // 模塊配置:配置loader(處理非 JavaScript 文件,好比 less、sass、jsx、圖片等等)等
plugins: [] // 插件的配置:打包優化、資源管理和注入環境變量
}
複製代碼
首先咱們往 webpack.config.js
添加點配置信息
const path = require('path')
module.exports = {
// 指定打包入口
entry: './src/index.js',
// 打包出口
output: {
path: path.resolve(__dirname, 'dist'), // 解析路徑爲 ./dist
filename: 'bundle.js'
}
}
複製代碼
上面咱們定義了打包入口 ./src/index.js
,打包出口爲 ./dist
, 打包的文件夾名字爲bundle.js
,執行npm run build
命令後,index.js 文件會被打包爲 bundle.js
文件。此時隨便創建一個 html 文件引用這個bundle.js
就能夠看到你在index.js
寫的代碼了。
path.resolve([...paths]) 方法會把一個路徑或路徑片斷的序列解析爲一個絕對路徑。
更多狀況下咱們不但願打包一次,就新建一次 html 文件來引用打包後的文件,這樣顯得不智能或者說當你打包的文件名修改後,引用路徑就會出錯。
這個時候咱們就可使用 html-webpack-plugin 插件來將 HTML 引用路徑和咱們的構建結果關聯起來。
npm install html-webpack-plugin -D
複製代碼
建立文件public/index.html
修改 webpack.config.js
文件
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // 配置輸出文件名和路徑
template: './public/index.html' // 配置要被編譯的html文件
})
]
}
複製代碼
從新執行 npm run build
, dist 目錄就會多個 index.html
並引入了 bundle.js
.
修改 webpack.config.js
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
//...
plugins: [
new HtmlWebpackPlugin({
filename: 'index.html', // 配置輸出文件名和路徑
template: './public/index.html', // 配置要被編譯的html文件
hash: true,
// 壓縮 => production 模式使用
minify: {
removeAttributeQuotes: true, //刪除雙引號
collapseWhitespace: true //摺疊 html 爲一行
}
})
]
}
複製代碼
咱們但願使用 webpack 來進行構建 css 文件,,爲此,須要在配置中引入 loader 來解析和處理 CSS 文件:
npm install style-loader css-loader -D
複製代碼
新建 src/assets/style/color.css
, 修改 webpack.config.js
文件:
module.exports = {
//...
module: {
/** * test: 匹配特定條件。通常是提供一個正則表達式或正則表達式的數組 * include: 匹配特定條件。通常是提供一個字符串或者字符串數組 * exclude: 排除特定條件 * and: 必須匹配數組中的全部條件 * or: 匹配數組中任何一個條件, * nor: 必須排除這個條件 */
rules: [
{
test: /\.css$/,
include: [path.resolve(__dirname, 'src')],
use: ['style-loader', 'css-loader']
}
]
}
//...
}
複製代碼
經由上述兩個 loader 的處理後,CSS 代碼會轉變爲 JS, 若是須要單獨把 CSS 文件分離出來,咱們須要使用 mini-css-extract-plugin 插件
npm i mini-css-extract-plugin postcss-loader autoprefixer -D
複製代碼
咱們在寫 css 時難免要考慮到瀏覽器兼容問題,如 transform
屬性,須要添加瀏覽器前綴以適配其餘瀏覽器。故使用到 postcss-loader
這個 loader, 下面則是相關的配置
webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
// ...
module: {
rules: [
{
test: /\.css$/,
include: [path.resolve(__dirname, 'src')],
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')]
}
}
]
}
]
},
plugins: [
//...
new MiniCssExtractPlugin({
filename: '[name].css',
chunkFilename: '[id].css'
})
]
}
複製代碼
開發中一般會用到一門預處理語言,這裏以less
爲例,經過less-loader
能夠打包 less 爲 css 文件
npm install less less-loader -D
複製代碼
新建 src/assets/style/index.less
, 而且在 src/index.js
中引入 import './assets/style/index.less'
配置 webpack.config.js
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
module.exports = {
module: {
rules: [
// ...
{
test: /\.less$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')] // 添加css中的瀏覽器前綴
}
},
'less-loader'
]
}
]
}
//...
}
複製代碼
執打包命令後就能夠發現 index.less
中寫的樣式會和color.css
同樣被打包進 main.css
中。
webpack@v4 升級踩坑: 關於使用 mini-css-extract-plugin
的注意點。
npm install file-loader url-loader -D
複製代碼
file-loader: 能夠用於處理不少類型的文件,它的主要做用是直接輸出文件,把構建後的文件路徑返回。
url-loader: 若是圖片較多,會發不少 http 請求,會下降頁面性能。url-loader
會將引入的圖片編碼,生成 dataURl。至關於把圖片數據翻譯成一串字符。再把這串字符打包到文件中,最終只須要引入這個文件就能訪問圖片了。固然,若是圖片較大,編碼會消耗性能。所以 url-loader
提供了一個 limit 參數,小於 limit 字節的文件會被轉爲 DataURl,大於 limit 的還會使用 file-loader
進行 copy。
module.exports = {
module: {
rules: [
// ...
{
test: /\.(png|jpg|gif)$/,
use: [
{
loader: 'url-loader',
options: {
outputPath: 'images/', //輸出到images文件夾
limit: 500 //是把小於500B的文件打成Base64的格式,寫入JS
}
}
]
}
]
}
//...
}
複製代碼
url-loader 和 file-loader 是什麼關係呢?
簡單地說,url-loader
封裝了 file-loader
。url-loader
不依賴於 file-loader
,即便用 url-loader
時,只須要安裝 url-loader
便可,不須要安裝 file-loader
,由於 url-loader
內置了 file-loader
。
經過上面的介紹,咱們能夠看到,url-loader 工做分兩種狀況:
有關 url-loader
和 file-loader
的解析:webpack 之圖片引入-加強的 file-loader:url-loader
Babel
是一個讓咱們可以使用 ES 新特性的 JS 編譯工具,咱們能夠在 webpack 中配置 Babel,以便使用 ES六、ES7 標準來編寫 JS 代碼。
Babel 7 的相關依賴包須要加上 @babel
scope。一個主要變化是 presets 設置由原來的 env
換成了 @babel/preset-env
, 能夠配置 targets
, useBuiltIns
等選項用於編譯出兼容目標環境的代碼。其中 useBuiltIns
若是設爲 "usage"
,Babel 會根據實際代碼中使用的 ES6/ES7 代碼,以及與你指定的 targets,按需引入對應的 polyfill
,而無需在代碼中直接引入 import '@babel/polyfill'
,避免輸出的包過大,同時又能夠放心使用各類新語法特性。
npm i babel-loader @babel/core @babel/preset-env -D
複製代碼
筆者這裏配的版本號以下
{
"babel-loader": "^8.0.4",
"@babel/core": "^7.1.2",
"@babel/preset-env": "^7.1.0"
}
複製代碼
babel-loader
根目錄下新建 .babelrc
文件
{
"presets": [
[
"@babel/preset-env",
{
"modules": false,
"targets": {
"browsers": ["> 1%", "last 2 versions", "not ie <= 8"]
},
"useBuiltIns": "usage"
}
]
]
}
複製代碼
修改 webpack.config.js
module.exports = {
module: {
rules: [
//...
{
test: /\.m?js$/,
exclude: /(node_modules|bower_components)/,
use: {
loader: 'babel-loader'
}
}
]
}
}
複製代碼
Babel 默認只轉換新的 JavaScript 句法(syntax),而不轉換新的 API ,好比 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全局對象,以及一些定義在全局對象上的方法(好比 Object.assign)都不會轉碼。
babel-polyfill: 如上述所說,對於新的 API,你可能須要引入 babel-polyfill 來進行兼容
關鍵點
babel-runtime 的做用:
babel-runtime
更像是分散的 polyfill 模塊,須要在各自的模塊裏單獨引入,藉助 transform-runtime
插件來自動化處理這一切,也就是說你不要在文件開頭 import 相關的 polyfill
,你只需使用,transform-runtime
會幫你引入。
對於開發應用來講,直接使用上述的按需 polyfill
方案是比較方便的,但若是是開發工具、庫的話,這種方案未必適合(babel-polyfill
是經過向全局對象和內置對象的 prototype
上添加方法實現的,會形成全局變量污染)。Babel 提供了另一種方案 transform-runtime
,它在編譯過程當中只是將須要 polyfill
的代碼引入了一個指向 core-js
中對應模塊的連接(alias)。關於這兩個方案的具體差別和選擇,能夠自行搜索相關教程,這裏再也不展開,下面提供一個 transform-runtime
的參考配置方案。
npm i @babel/plugin-transform-runtime -D
npm i @babel/runtime -S
複製代碼
修改 .babelrc
{
//...
"plugins": ["@babel/plugin-transform-runtime"]
}
複製代碼
每次打包,都會生成項目的靜態資源,隨着某些文件的增刪,咱們的 dist 目錄下可能產生一些再也不使用的靜態資源,webpack 並不會自動判斷哪些是須要的資源,爲了避免讓這些舊文件也部署到生產環境上佔用空間,因此在 webpack 打包前最好能清理 dist 目錄。
npm install clean-webpack-plugin -D
複製代碼
修改 webpack.config.js
文件
const CleanWebpackPlugin = require('clean-webpack-plugin')
module.exports = {
plugins: [new CleanWebpackPlugin(['dist'])]
}
複製代碼
假如你 a.js
和 b.js
都 import 了 c.js
文件,這段代碼就冗雜了。爲何要提取公共代碼,簡單來講,就是減小代碼冗餘,提升加載速度。
module.exports = {
//...
optimization: {
splitChunks: {
cacheGroups: {
commons: {
// 抽離本身寫的公共代碼
chunks: 'initial',
name: 'common', // 打包後的文件名,任意命名
minChunks: 2, //最小引用2次
minSize: 0 // 只要超出0字節就生成一個新包
},
styles: {
name: 'styles', // 抽離公用樣式
test: /\.css$/,
chunks: 'all',
minChunks: 2,
enforce: true
},
vendor: {
// 抽離第三方插件
test: /node_modules/, // 指定是node_modules下的第三方包
chunks: 'initial',
name: 'vendor', // 打包後的文件名,任意命名
// 設置優先級,防止和自定義的公共代碼提取時被覆蓋,不進行打包
priority: 10
}
}
}
}
}
複製代碼
hash 是幹嗎用的? 咱們每次打包出來的結果可能都是同一個文件,那我上線的時候是否是要替換掉上線的 js,那我怎麼知道哪是最新的呢,咱們通常會清一下緩存。而 hash 就是爲了解決這個問題而存在的
咱們此時在改一些 webpack.config.js 的配置
module.exports = {
//...
output: {
path: path.resolve(__dirname, 'dist'),
filename: '[name].[hash:8].js'
},
//...
plugins: [
new MiniCssExtractPlugin({
filename: '[name].[hash:8].css',
chunkFilename: '[id].[hash:8].css'
})
]
}
複製代碼
若是咱們能夠精簡 resolve
配置,讓 webpack
在查詢模塊路徑時儘量快速地定位到須要的模塊,不作額外的查詢工做,那麼 webpack
的構建速度也會快一些
module.exports = {
resolve: {
/** * alias: 別名的配置 * * extensions: 自動解析肯定的擴展, * 好比 import 'xxx/theme.css' 能夠在extensions 中添加 '.css', 引入方式則爲 import 'xxx/theme' * @default ['.wasm', '.mjs', '.js', '.json'] * * modules 告訴 webpack 解析模塊時應該搜索的目錄 * 若是你想要添加一個目錄到模塊搜索目錄,此目錄優先於 node_modules/ 搜索 * 這樣配置在某種程度上能夠簡化模塊的查找,提高構建速度 @default node_modules 優先 */
alias: {
'@': path.resolve(__dirname, 'src'),
tool$: path.resolve(__dirname, 'src/utils/tool.js') // 給定對象的鍵後的末尾添加 $,以表示精準匹配
},
extensions: ['.wasm', '.mjs', '.js', '.json', '.jsx'],
modules: [path.resolve(__dirname, 'src'), 'node_modules']
}
}
複製代碼
上面講到了都是如何打包文件,可是開發中咱們須要一個本地服務,這時咱們可使用 webpack-dev-server
在本地開啓一個簡單的靜態服務來進行開發。
webpack-dev-server
是 webpack 官方提供的一個工具,能夠基於當前的 webpack 構建配置快速啓動一個靜態服務。當 mode
爲 development
時,會具有 hot reload
的功能,即當源碼文件變化時,會即時更新當前頁面,以便你看到最新的效果。...
npm install webpack-dev-server -D
複製代碼
package.json 中 scripts 中添加
"start": "webpack-dev-server --mode development"
複製代碼
默認開啓一個本地服務的窗口 http://localhost:8080/ 便於開發
咱們能夠對 webpack-dev-server
作針對性的配置
module.exports = {
// 配置開發服務器
devServer: {
port: 1234,
open: true, // 自動打開瀏覽器
compress: true // 服務器壓縮
//... proxy、hot
}
}
複製代碼
模塊熱替換(HMR - Hot Module Replacement
)功能會在應用程序運行過程當中替換、添加或刪除模塊,而無需從新加載整個頁面。主要是經過如下幾種方式,來顯著加快開發速度:
上面咱們 npm start
後修改一次文件,頁面就會刷新一次。這樣就存在很大問題了,好比咱們使用 redux
, vuex
等插件,頁面一刷新那麼存放在 redux
, vuex
中的東西就會丟失,很是不利於咱們的開發。
HMR 配合 webpack-dev-server ,首先咱們配置下 webpack.config.js
const webpack = require('webpack')
module.exports = {
devServer: {
//...
hot: true
},
plugins: [
new webpack.HotModuleReplacementPlugin()
//...
]
}
複製代碼
配置後還不行,由於 webpack 還不知道你要更新哪裏, 修改 src/index.js
文件, 添加
if (module.hot) {
module.hot.accept()
}
複製代碼
重啓服務,npm start
以後,修改引入 index.js
文件後,頁面就不會從新刷新了,這便實現了 HMR
可是可是有個問題是,你修改 css/less 等樣式文件並未發生改變, what ?
HMR 修改樣式表 須要藉助於 style-loader
, 而咱們以前用的是 MiniCssExtractPlugin.loader
, 這也好辦,修改其中一個 rules 就能夠了,咱們能夠試試改
module.exports = {
module: {
rules: [
{
test: /\.less$/,
use: [
// MiniCssExtractPlugin.loader,
'style-loader',
'css-loader',
{
loader: 'postcss-loader',
options: {
plugins: [require('autoprefixer')] // 添加css中的瀏覽器前綴
}
},
'less-loader'
]
}
]
}
}
複製代碼
這樣咱們修改 less 文件就會發現 HMR 已經實現了。
其實,咱們能夠發現,dev 下配置的 loader 爲 style-loader
, 而生產環境下則是須要 MiniCssExtractPlugin.loader
這就涉及到了不一樣環境之間的配置。能夠經過 process.env.NODE_ENV
獲取當前是開發環境或者是生產環境,而後配置不一樣的 loader,這裏就不作展開了。下一篇文章打算在作一個 react-cli
或者 vue-cli
的配置,將開發環境的配置與生產環境的配置分開爲不一樣的文件。
前面講到的知識都是 webpack 的一些基礎的知識,更多的資料能夠查詢webpack 中文官網,官網講的比較詳細,我這裏也是講最常的配置,也是一篇入門系列的文章,文中涉及的知識點還有不少地方還須要完善,譬如 優化 webpack 的構建速度, 減少打包的體積等等。
學習 webpack 4.0
還須要多實踐,多瞎搞,筆者也是剛剛學習 webpack 的配置,不對之處請各位指出。
下一篇文章打算從零配置一個腳手架,以加深本身對 webpack 的理解。
本文產生的代碼:webpack-dev