// 一個常見的`webpack`配置文件
const webpack = require('webpack');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
module.exports = {
entry: __dirname + "/app/main.js", //已多次提及的唯一入口文件
output: {
path: __dirname + "/build", //打包后的文件存放的地方
filename: "bundle-[hash].js" //打包后輸出文件的文件名
},
devtool: 'none',
devServer: {
contentBase: "./public", //本地服務(wù)器所加載的頁面所在的目錄
historyApiFallback: true, //不跳轉(zhuǎn)
inline: true, //構(gòu)建變化后自動刷新網(wǎng)頁實現(xiàn)實時預(yù)覽
hot: true
},
module: {
rules: [{
test: /(\.jsx|\.js)$/,
use: {
loader: "babel-loader"
},
exclude: /node_modules/
}, {
test: /\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: [{
loader: "css-loader",
options: {
modules: true,
localIdentName: '[name]__[local]--[hash:base64:5]'
}
}, {
loader: "postcss-loader"
}],
})
}
}
]
},
plugins: [
new webpack.BannerPlugin('123'),
new HtmlWebpackPlugin({
template: __dirname + "/app/index.tmpl.html" //new 一個這個插件的實例,并傳入相關(guān)的參數(shù)
}),
new webpack.optimize.OccurrenceOrderPlugin(),
new webpack.optimize.UglifyJsPlugin(),
new ExtractTextPlugin("style.css")
]
};
注:“_dirname”是node.js的一個全局變量,指向當前執(zhí)行腳本所在的目錄。
幾個核心概念。
- Entry: 入口,webpack執(zhí)行構(gòu)建的第一步將從Entry開始,可抽象成輸入
- Module: 模塊,在Webpack里一切皆模塊,一個模塊對應(yīng)一個文件。Webpack會從配置的Entry開始遞歸找出所有依賴的模塊。
- Chunk: 代碼塊,一個Chunk由多個模塊組合而成,用于代碼合并與分割。
- Loader: 模塊轉(zhuǎn)換器,用于將模塊的原內(nèi)容按照需求轉(zhuǎn)換成新內(nèi)容。項目中需要的每個Loader都需要安裝。
- Plugin: 擴展插件,在Webpack構(gòu)建流程中的特定時機注入擴展邏輯,來改變構(gòu)建結(jié)果或做我們想要的事情。
- Output: 輸出結(jié)果,在Webpack經(jīng)過一系列處理并得出最終想要的代碼后輸出結(jié)果。
Webpack在啟動后會從Entry里面配置的Module開始,遞歸解析Entry依賴的所有Module。每找到一個Module,就會根據(jù)配置的Lodaer去找出對應(yīng)的轉(zhuǎn)換規(guī)則,對Module進行轉(zhuǎn)換后,再解析出當前Module依賴的Module。這些模塊會以Entry為單位進行分組,一個Entry及其所有依賴的Module被分到一個組也就是一個Chunk。最后,Webpack會將所有Chunk轉(zhuǎn)換成文件輸出。在整個流程中,Webpack會在恰當?shù)臅r機執(zhí)行Plugin里定義的邏輯。
DevServer
使用DevServer,DevServer會啟動一個HTTP服務(wù)器用于服務(wù)網(wǎng)頁請求,同時會幫助啟動Webpack,并接收Webpack發(fā)出的文件變更信號,通過WebSocket協(xié)議自動刷新網(wǎng)頁做到實時預(yù)覽。
執(zhí)行打包任務(wù)
可以在package.json里對scripts對象進行設(shè)置即可。
{
"scripts": {
"start": "webpack-dev-server --open --hot --progress --colors --host localhost",
"build": "rm -rf ./dist && webpack --progress --colors"
},
}
npm的start命令是一個特殊的腳本名稱,其特殊性表現(xiàn)在,在命令行中使用npm start就可以執(zhí)行其對于的命令,如果對應(yīng)的此腳本名稱不是start,想要在命令行中運行時,需要這樣用npm run {script name} 如 npm run build
--open --host localhost
//執(zhí)行npm start后自動打開瀏覽器
resolve.alias && ProvidePlugin
有時候我們在項目中會需要頻繁引入同一個路徑的文件,如果需要引入的次數(shù)特別多,我們就得在每一次引入都要寫一長串的地址,那么我們有沒有什么方法可以偷點懶呢,我們可以通過調(diào)整webpack里的配置達到“偷一點小懶”的目的。
resolve.alias這個配置項相當于為文件目錄配置一個別名
使用resolve.alias配置的用法如下
module.exports = {
entry:
{
main:'./main.js',
},
output: {
path:__dirname+'/dist',
filename: '[name].js'
},
resolve:{
//配置別名,在項目中可縮減引用路徑
alias: {
vue$: 'vue/dist/vue.esm.js',
'@': resolve('src'),
'&': resolve('src/components'),
'api': resolve('src/api'),
'assets': resolve('src/assets')
}
},
plugins: [
]
};
這樣配置后在使用的時候就可以直接
import http from '@/utils/http'
代替
import http from 'src/utils/http'
reslove.alias可以讓我們不用重復(fù)的去寫一長串的路徑,但是在使用的時候還是得引入,如果我們連引入都懶得引入呢?webpack為我們提供了ProvidePlugin這個幫我們解決問題的插件
那么ProvidePlugin要怎么使用呢?
webpack.config.js
const webpack = require('webpack')
module.exports = {
entry:
{
main:'./main.js',
},
output: {
path:__dirname+'/dist',
filename: '[name].js'
},
resolve:{
//配置別名,在項目中可縮減引用路徑
alias: {
}
},
plugins: [
//提供全局的變量,在模塊中使用無需用require引入
new webpack.ProvidePlugin({
$config: [resolve(`src/data/config/${process.env.CONFIG_ENV}.env.js`), 'default'],
}),
]
};
使用的時候就可以寄直接將 $config作為一個全局變量來使用
編寫一個plugin
plugin可以監(jiān)聽webpack處理過程中的關(guān)鍵事件,深度集成進webpack的編譯器,可以說plugin的執(zhí)行層面是整個構(gòu)建過程。Plugin系統(tǒng)是構(gòu)成webpack的主干,webpack自身也基于plugin系統(tǒng)搭建,webpack有豐富的內(nèi)置插件和外部插件,并且允許用戶自定義插件。
插件的組成部分
- 一個javaScript命名函數(shù),并暴露出去
- 在插件函數(shù)的prototype上定義一個apply方法,注入complier對象
- 指定一個綁定到webpack自身的事件鉤子
- 處理webpack內(nèi)部實例的特定數(shù)據(jù)
- 功能完成后調(diào)用webpack提供的回調(diào)
function MyPlugin(options) {}
// 2.函數(shù)原型上的 apply 方法會注入 compiler 對象
MyPlugin.prototype.apply = function(compiler) {
// 3.compiler 對象上掛載了相應(yīng)的 webpack 事件鉤子 4.事件鉤子的回調(diào)函數(shù)里能拿到編譯后的 compilation 對象
compiler.plugin('emit', (compilation, callback) => {
...
})
}
// 1.獨立的 JS 模塊,暴露相應(yīng)的函數(shù)
module.exports = MyPlugin
Compiler 和 Compilation
compiler對象和compilation對象是webpack插件開發(fā)中最重要的兩個資源,那他們分別是什么呢?
compiler 對象代表了完整的 webpack 環(huán)境配置。這個對象在啟動 webpack 時被一次性建立,并配置好所有可操作的設(shè)置,包括 options,loader 和 plugin。當在 webpack 環(huán)境中應(yīng)用一個插件時,插件將收到此 compiler 對象的引用??梢允褂盟鼇碓L問 webpack 的主環(huán)境。
compilation 對象代表了一次資源版本構(gòu)建。當運行 webpack 開發(fā)環(huán)境中間件時,每當檢測到一個文件變化,就會創(chuàng)建一個新的 compilation,從而生成一組新的編譯資源。一個 compilation 對象表現(xiàn)了當前的模塊資源、編譯生成資源、變化的文件、以及被跟蹤依賴的狀態(tài)信息。compilation 對象也提供了很多關(guān)鍵時機的回調(diào),以供插件做自定義處理時選擇使用。
compiler 對象
compiler 即 webpack 的編輯器對象,在調(diào)用 webpack 時,會自動初始化 compiler 對象,源碼如下:
// webpack/lib/webpack.js
const Compiler = require("./Compiler")
const webpack = (options, callback) => {
...
options = new WebpackOptionsDefaulter().process(options) // 初始化 webpack 各配置參數(shù)
let compiler = new Compiler(options.context) // 初始化 compiler 對象,這里 options.context 為 process.cwd()
compiler.options = options // 往 compiler 添加初始化參數(shù)
new NodeEnvironmentPlugin().apply(compiler) // 往 compiler 添加 Node 環(huán)境相關(guān)方法
for (const plugin of options.plugins) {
plugin.apply(compiler);
}
...
}
compilation 對象
結(jié)合源碼來理解下上面這段話,首先 webpack 在每次執(zhí)行時會調(diào)用 compiler.run() (源碼位置),接著追蹤 onCompiled 函數(shù)傳入的 compilation 參數(shù),可以發(fā)現(xiàn) compilation 來自構(gòu)造函數(shù) Compilation。
// webpack/lib/Compiler.js
const Compilation = require("./Compilation");
newCompilation(params) {
const compilation = new Compilation(this);
...
return compilation;
}
path.join()與path.resolve()的區(qū)別
path 是 node.js內(nèi)置的package,用來處理路徑
-
path.join([path1],[path2][,...])
用于連接路徑,對參數(shù)里的路徑片段就行拼接
-
path.resolve([from...][,to])
將to解析成絕對路徑
path.resolve(_dirname, '/img')
console.log(_dirname) //當前文件所在文件夾的絕對路徑