webpack 是什么?
官方文檔里的定義:
webpack 是一個現代 JavaScript 應用程序的靜態模塊打包器(module bundler)。當 webpack 處理應用程序時,它會遞歸地構建一個依賴關系圖(dependency graph),其中包含應用程序需要的每個模塊,然后將所有這些模塊打包成一個或多個 bundle。
為什么需要 Webpack?
現在的網頁可看做是功能豐富的應用,它們包含大量的 JavaScript 代碼和一些開源的依賴包,如何管理這些文件也是一個問題。有了 JavaScript 模塊化編程,我們可以把復雜的程序細化為小的文件,WebPack 幫助我們管理這些文件的依賴關系,把各種模塊打包合并成一個文件輸出給瀏覽器。在打包的過程中還會對資源做一些處理,比如把 Typescript 編譯成 JavaScript、sass 編譯成 css 等。
使用 Webpack
WebPack 有四個核心概念:
- 入口 (entry)
- 輸出 (output)
- Loader
- 插件 (plugins)
下面結合 Vue 項目模板介紹 Webpack 基本的使用方法
1. 準備工作
按照 Vue 教程 創建一個新項目
# 全局安裝 vue-cli
$ npm install --global vue-cli
# 創建一個基于 webpack 模板的新項目
$ vue init webpack my-project
# 安裝依賴,走你
$ cd my-project
$ npm install
$ npm run dev
進入項目的 build
目錄,這里有三個文件用于設置 WebPack:
- webpack.base.conf.js
- webpack.dev.conf.js
- webpack.prod.conf.js
看名字就能猜到它們使用環境:webpack.dev.conf.js
保存的是開發環境的配置,webpack.prod.conf.js
是生成環境的配置,webpack.base.conf.js
是這兩個環境公用的配置。
webpack.base.conf.js
文件不到 100 行,主要內容在 module.exports = { }
中:
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
},
module: {
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test'), resolve('node_modules/webpack-dev-server/client')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
node: {
// prevent webpack from injecting useless setImmediate polyfill because Vue
// source contains it (although only uses it if it's native).
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
2. 入口 (entry)
定義在 webpack.base.conf.js
中:
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
這里指定 ./src/main.js
為入口文件,Webpack 已這文件為起點,找出它依賴的模塊,處理每個依賴項,然后輸出到 bundle 文件中。單頁應用指定一個入口就可以了,多頁應用需要指定多個入口,具體語法參考文檔 入口起點
3. 出口
output
屬性告訴 webpack 在哪里輸出它所創建的 bundles,以及如何命名這些文件。webpack.base.conf.js
中的出口定義:
module.exports = {
context: path.resolve(__dirname, '../'),
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
filename: '[name].js',
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
xxx
filename
和 path
屬性,用來告訴 webpack bundle 的名稱,以及我們想要生成(emit)到哪里。
config.build.assetsRoot
這個屬性定義在 ./config/index.js
文件中,
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'),
// Paths
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
實際值是 ../dist/index.html
, 運行 npm run build
后會在生成 dist 目錄和 index.html
文件。
注意到 filename
的名稱不是固定的,使用了 [name]
占位符,webpack 編譯后 name
會被替換為該模塊的名稱,類似的,還可以使用其它占位符作為 filename
,如 [hash]
、[chunkhash]
、[id]
、[query]
等,它們的含義在 output-filename 文檔中有詳細介紹。
publicPath 項指定在瀏覽器中所引用的「此輸出目錄對應的公開 URL」。這里通過判斷 process.env.NODE_ENV
的值來區分開發和生成環境。和 config.build.assetsRoot
類似,config.build.assetsPublicPath
和 config.dev.assetsPublicPath
都是定義在 ./config/index.js
文件中,它們的值都是 /
。 process.env.NODE_ENV
有時定義在哪里呢?process.env 是 Node 的環境變量,這篇文章中有介紹。這里的 process.env.NODE_ENV 定義在config目錄下,dev.env.js
、prod.env.js
、test.env.js
三個文件中都有定義,分別對應開發、生成、和測試環境。
4. loader
Webpack 自身只理解 JavaScript,通常項目還會包含 CSS、圖片、文本等其它資源,loader 可以將這些文件轉換為 webpack 能夠處理的有效模塊,然后你就可以利用 Webpack 的打包能力,對它們進行處理。loader 的配置在 module.rules 屬性里,以第一個 loader 配置為例:
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
}
- test: 識別需要轉換的文件,這個正則表示 ".vue" 后綴類型文件
- loader: 設置 loader 名稱,常用的 loader 可以在 這里 查詢
- options: 可以是字符串或者對象,它的值會被傳遞到 loader 中,需要參考各 loader 的使用文檔,這里
這個配置告訴 Webpack,遇到 require
或者 import
.vue 類型文件時,先交給 vue-loader
轉換一下,然后再對它打包。
5. Plugins
插件可以處理多種類型的任務,從打包優化和壓縮,一直到重新定義環境中的變量。插件的使用方法也很簡單,首先 require
,然后 new
一個實例放到 plugins 數組中。Vue 模板項目中的插件配置分別在 webpack.dev.conf.js
和 webpack.prod.conf.js
兩個文件中,以 webpack.dev.conf.js
中配置為例:
plugins: [
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
new webpack.HotModuleReplacementPlugin(),
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
new webpack.NoEmitOnErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.dev.assetsSubDirectory,
ignore: ['.*']
}
])
]
從上到下依次為:
- DefinePlugin: 通過配置了 DefinePlugin,這里面的標識 (
process.env
) 就相當于全局變量,業務代碼可以直接使用配置的標識。 - HotModuleReplacementPlugin: 模塊熱替換插件,在應用程序運行過程中替換、添加或刪除模塊,而無需重新加載整個頁面,加快開發速度。
- NoEmitOnErrorsPlugin: 跳過編譯時出錯的代碼并記錄,使編譯后運行時的包不會發生錯誤。
- HtmlWebpackPlugin: 根據你提供的 html 模板 生成 html。
- CopyWebpackPlugin: 拷貝靜態資源到指定目錄
6. 其它配置項目
注意到 webpack.base.conf.js
文件中,在 output
屬性下面,還有個 resolve
屬性,它有什么作用呢?
resolve: {
extensions: ['.js', '.vue', '.json'],
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
}
}
我們在項目中 import 的模塊有的是自己定義的,有的是第三方的庫,resolver 幫助 webpack 找到這些模塊的絕對路徑。
- alias: 上述配置中定義了兩個別名,
vue
指向 node_modules 下的vue/dist/vue.esm.js
文件,在使用的時候直接import Vue from 'vue'
,webpack 就能夠找到該文件了。@
指向了src
目錄,引用src
目錄下的模塊可以這樣寫import HelloWorld from '@/components/HelloWorld'
。 - extensions: 在
import
模塊時不帶擴展名,webpack 依然可找到對應的文件,具體哪些文件可以不帶呢?就是在這里定義的。extensions 數組有.js
、.vue
、.json
,所以我們引用這些文件時就可以不加擴展名了。
總結
對于剛接觸前端項目的新人,了解 Webpack 的基本配置很重要。這里只是簡單介紹了 webpack 最基本的功能,還有如 devserver 等其它常用配置就留給你去探索吧。官方文檔真的很全,各種屬性的使用方法都有詳細的介紹,一定不要浪費這么好的資源。