原文連結:banggan.github.io/2019/05/09/…
Webpack核心概念解析
終於忙完了論文,可以愉快的開始學習了,重拾起重學前端、webpack以及Vue的原始碼解讀作為入職前的複習吧。整個webpack系列將分成五個大的部分進行,以webpack4.0為文件進行解讀,從簡單的概念解讀到最後的實現。 整個知識點涉及範圍:
loader
使用loader來預處理檔案,把不同的靜態資源(模組的結尾不是js的模組)打包成js檔案
loader打包靜態資源
打包圖片
- 安裝使用file-loader實現:
npm install file-loader -D
- 在webpack.config.js中新增loader的配置
module.exports = {
//打包專案的入口檔案
entry: './src/index.js',
module:{
rules:[{
test:/\.(jpg|png|gif)$/,//打包以jpg、png、gif結尾的所有圖片檔案
use:{
loader:'file-loader',
options:{//placeholder 佔位符
name:'[name]_[hash].[ext]',//保持原圖片的名字+hash值和字尾,主要單引號
outputPath:'image/'//打包圖片的位置
}
}
}]
}
}
複製程式碼
- 更多的有關於file-loader的配置見文件
打包圖片成base64格式
url-loader基本能實現file-loader的打包功能,適用於小圖片的打包
- 好處:圖片打包成js檔案,不用載入圖片的地址,頁面快速顯示
- 壞處:圖片過大導致js檔案過大
所以,當圖片的大小小於limit值時會把圖片打包成base64格式,大於limit值則按照file-loader打包成圖片檔案
- 安裝使用url-loader實現:
npm install url-loader -D
- 在webpack.config.js中新增loader的配置
module.exports = {
module:{
rules:[{//打包以jpg、png、gif結尾的所有圖片檔案
test:/\.(jpg|png|gif)$/,
use:{
loader:'url-loader',
options:{//placeholder 佔位符
name:'[name]_[hash].[ext]',//保持原圖片的名字+hash值和字尾,主要單引號
outputPath:'image/',//打包圖片的位置
limit:2048
}
}]
}
}
複製程式碼
- 更多的有關於url-loader的配置見文件
打包樣式css檔案
需要使用css-loader、style-loader
- css-loader:分析幾個css檔案的關係,合併css檔案
- style-loader:將css-loader合併的css內容掛載在頁面的head部分
實現方式:
- 安裝loader實現:
npm install css-loader style-loader -D
- 在webpack.config.js中新增loader的配置
module.exports = {
module: {
rules: [{//打包css檔案
test:/\.css$/,
use:['style-loader','css-loader']
}]
}
}
複製程式碼
- 更多的有關於css-loader的配置見文件
打包樣式scss檔案
需要使用sass-loader、node-sass
- 安裝loader實現:
npm install sass-loader node-sass -D
- 在webpack.config.js中新增loader的配置
module.exports = {
module: {
rules: [{
test: /\.scss$/,
use:['style-loader','css-loader','sass-loader']
}]
}
};
複製程式碼
在配置中,有三個loader,執行順序是從下到上,從右到左。在打包scss檔案時,首先執行sass-loader:對sass翻譯成css檔案,在掛載到css-loader,最後style-loader.
- 更多的有關於sass-loader的配置見文件
為樣式新增不同瀏覽器的字首
為了相容不同的瀏覽器,在寫樣式的時候需要加上適用不同瀏覽器的字首,如-o、-webkit、-moz等
-安裝loader實現:npm install postcss-loader autoprefixer -D
-在根目錄建立postcss.config.js
moudle.exports ={
plugins:[
require('autoprefixer')
]
}
複製程式碼
- 在webpack.config.js中新增loader的配置
module.exports = {
module: {
rules: [{
test:/\.scss$/,
use:[
'style-loader',
'css-loader',
'sass-loader',
'postcss-loader']
}]
}
}
複製程式碼
- 更多的有關於postcss-loader的配置見文件
css-loader新增不同的配置
css模組化打包
- 場景:在檔案引入的scss不僅影響當前的檔案,還影響當前檔案引入的其他js檔案,造成樣式衝突
- 實現:css只在當前模組類有效,在配置中新增
modules:true
開啟css的模組化打包,在引入的時候注意區分
scss檔案的巢狀引用
-
場景:scss檔案通過import引入其他scss檔案,導致打包的時候引入的scss檔案打包錯誤
-
實現:
importLoader:2
-
在webpack.config.js中新增loader的配置
module:{
rules:[{//打包scss檔案
test:/\.scss$/,
use:[
'style-loader',
{
loader:'css-loader',
options:{
importLoaders:2,//index.scss中通過import引入其他的scss檔案,引入的scss檔案在打包的時候也將依次經過所有的loader
modules:true
}
},
'sass-loader',
'postcss-loader']
}]
}
複製程式碼
打包字型圖示檔案
在阿里巴巴向量圖示庫中,把需要的字型圖示下載到本地,解壓。將iconfont.eot iconfont.svg iconfont.ttf iconfont.woff 四種圖片檔案放入到專案中,在src中新建一個放字型圖示的資料夾font。將iconfont.css檔案拷貝到專案中,修改對應字型的引用路徑。
- 需要安裝 file-loader:
npm i file-loader -D
- 在webpack.config.js中新增loader的配置
module.exports = {
...
module: {
rules: [{
test: /\.(eot|ttf|svg|woff)$/,
use:{
loader:'file-loader'
}
},
]
}]
}
}
複製程式碼
- 更多的有關於載入字型的配置見文件
打包資料檔案
如遇到json、scv、xml檔案需要打包時,使用csv-loader 和 xml-loader實現。
- 安裝:npm install csv-loader xml-loader -D
- 在webpack.config.js中新增loader的配置
module.exports = {
module: {
rules: [{
test: /\.(csv|tsv)$/,
use: [
'csv-loader'
]
},
{
test: /\.xml$/,
use: [
'xml-loader'
]
}]
}
}
複製程式碼
- 更多的有關於載入資料的配置見文件
plugins
loaders可以將各個型別的靜態資源打包成webpack能處理的模組,而plugins有更強大的功能。它可以從打包優化和壓縮,一直到重新定義環境中的變數。
plugin可以在webpack執行到某一個時刻,自動完成一些事情。
自動生成html檔案,並引入打包生成的js檔案到生成的html檔案中
- 安裝使用HtmlWebpackPlugin實現:
npm install html-webpack-plugin -D
- 在webpack.config.js中新增loader的配置
在src中建立一個html的模板,在HtmlWebpackPlugin的配置中引入該模板,打包後生成和模板類似的html檔案並引入打包的js檔案。
const HtmlWebpackPlugin = require('html-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
})]
}
複製程式碼
- 更多的有關於HtmlWebpackPlugin的配置見文件
自動清除上一次打包的dist檔案
先刪除上一次打包的dist檔案,再執行打包
- 安裝使用CleanWebpackPlugin 實現:
npm install clean-webpack-plugin -D
- 在webpack.config.js中新增loader的配置
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const path = require('path');
module.exports = {
entry: 'index.js',
output: {
path: path.resolve(__dirname, './dist'),
filename: 'index_bundle.js'
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']), // 在打包之前,可以刪除dist資料夾下的所有內容
]
}
複製程式碼
- 更多的有關於CleanWebpackPlugin的配置見文件
Entry與Output的基礎配置
- 需求:打包多個入口檔案,對應的在出口的配置中注意命名filename避免出口檔名字衝突----使用佔位符)來確保每個檔案具有唯一的名稱
- 需求:打包後的檔案,作為後端的介面檔案,靜態資源需要上傳到cdn,在output中進行配置,提前加入cdn的地址。在編譯時,如果不知道publicPath的地址,可以留空,在入口起點檔案執行時動態設定。
__webpack_public_path__ = myRuntimePublicPath
module.exports = {
mode: 'development',
entry: {
main: './src/index.js',
sub: './src/index.js'
},
plugins: [new HtmlWebpackPlugin({
template: 'src/index.html'
}), new CleanWebpackPlugin(['dist'])],
output: {
publicPath: 'http://cdn.com.cn', //加入cdn地址
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
複製程式碼
- 更多的有關於output的配置見文件
SourceMap的配置
SourceMap是一個對映關係,打包檔案和原始檔的對映關係,用於開發者的除錯。
在devtool中進行設定:devtool: 'source-map'
打包速度會降低,在dist裡面會有map對映檔案
常用設定devtool說明:
- none:在開發者模式下,預設開啟sourcemap,將其關閉
- inline字首:不單獨生成map檔案,把對應的map檔案以base64的形式直接打包到js檔案。
- cheap字首:sourcemap和打包後的js同行顯示,並沒有對映到列忽略源自 loader 的 source。 map,並且僅顯示轉譯後的程式碼,所以打包速度相對來說較快。程式碼出錯提示不用精確顯示第幾行的第幾個字元出錯,只顯示第幾行出錯,會提高一些效能,
- moudle字首:不僅對映業務程式碼,還會包括loader、第三方模組的錯誤。
- eval字首:打包速度最快。
development環境推薦使用: devtool: 'cheap-module-eval-source-map'
production環境推薦使用: devtool: 'cheap-module-source-map'
- 更多的有關於devtool文件
使用WebpackDevServer提升開發效率
場景:每次在src裡編寫完程式碼都需要手動重新執行 npm run bundle,如何自動解決?
-安裝:npm install webpack-dev-server –D
- devServer引數說明
- contentBase :配置開發服務執行時的檔案根目錄
- open :自動開啟瀏覽器
- host:開發伺服器監聽的主機地址
- compress :開發伺服器是否啟動gzip等壓縮
- port:開發伺服器監聽的埠
- 在 webpack.config.js 中,加 devServer
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const CleanWebpackPlugin = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080
proxy:{//配置跨域,訪問的域名會被代理到本地的3000埠
'/api': 'http://localhost:3000'
}
}
}
複製程式碼
- 在 package.json 中配置
{
"name": "banggan",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"bundle": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server",
},
}
複製程式碼
- 更多的有關於devServer文件
如何實現自己寫一個類似webpackdevserver的工具
- 在package.json 中配置
建立一個新的指令,
npm run server
執行自己寫的類似webpackdevserver的工具
{
"name": "banggan",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"bundle": "webpack",
"watch": "webpack --watch",
"start": "webpack-dev-server",
"server" : "node server.js"
},
}
複製程式碼
- 安裝express:
npm install express webpack-dev-middleware -D
- 在根目錄建立一個server.js
- 在node中直接使用webpack:
webpack(config)
const express = require('express'); //引入express
const webpack = require('webpack');//引入webpack
const webpackDevMiddleware = require('webpack-dev-middleware');
const config = require('./webpack.config.js');//引入配置檔案
const complier = webpack(config); //編譯一次嗎,打包一次程式碼
const app = express();//建立express例項
app.use(webpackDevMiddleware(complier, {}));
app.listen(3000, () => {//監聽3000埠
console.log('server is running');
});
複製程式碼
Hot Module Replacement熱模組更新
-
場景:在程式執行中。替換、新增、替換某個模組,不需要重新載入整個頁面,實現互動時更新。
-
在 webpack.config.js 中,新增配置
module.exports = {
mode: 'development',
devtool: 'cheap-module-eval-source-map',
devServer: {
contentBase: './dist',
open: true,
port: 8080,
hot: true,//開啟熱更新功能
hotOnly: true//如果html功能沒有實現,也不讓瀏覽器重新整理
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),
new webpack.HotModuleReplacementPlugin()//使用熱模組外掛
],
}
複製程式碼
- 在main.js檔案中,使用 HotModuleReplacementPlugin 啟用模組的熱替換功能。介面暴露在moudle.hot屬性下面
//如果模組啟用了HMR,就可以用 module.hot.accept(),監聽模組的更新。
if (module.hot) {
module.hot.accept('./library.js', function() {
// 使用更新過的 library 模組執行某些操作...
})
}
複製程式碼
//拒絕給定依賴模組的更新,使用 'decline' 方法強制更新失敗。
module.hot.decline(
dependencies // 可以是一個字串或字串陣列
)
複製程式碼
注意:引入css檔案時,用框架Vue,React 時,不需要寫 module.hot.accept(),因為在使用css-loader,vue-loader,babel-preset時,均配置好了HMR,不需要自己重新寫 如果檔案沒有內建HMR,需要自己手動寫監聽模組更新程式碼
結合Bable處理ES6語法
- 場景:程式碼中含有ES6/ES7的程式碼,為了讓低版本的瀏覽器相容程式碼,需要使用Bable進行轉換
- 安裝:Bable官網
//preset-env語法轉換
npm install babel-loader @babel/core @babel/preset-env -D
//相容低版本瀏覽器的語法,函式的補充
npm install --save @babel/polyfill
複製程式碼
- 在 webpack.config.js 中,新增配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
loader: "babel-loader" ,
options:{
"presets": [["@babel/preset-env",{
targets: {
edge: "17",
firefox: "60",
chrome: "67",
safari: "11.1",
},//執行在大於**版本的瀏覽器上,,已經支援es6的高瀏覽器不需要轉換為es5
useBuiltIns:'usage' //按需新增polyfill,把業務程式碼中的新語法新函式都轉成低版本瀏覽器相容的
}]]
}
}
]
}
複製程式碼
- 在src目錄下的index.js中頂部位置匯入
import "@babel/polyfill";
注意如果不是打包業務程式碼,而是寫的類庫、或者z元件庫的時候不能使用@babel/polyfill實現,因為會導致宣告的變數變成全域性變數,汙染全域性環境。使用
plugin-transform-runtime
實現
- 安裝外掛
npm install --save-dev @babel/plugin-transform-runtime
npm install --save @babel/runtime
npm install --save @babel/runtime-corejs2
複製程式碼
- 在 webpack.config.js 中,新增配置
module: {
rules: [
{
test: /\.js$/,
exclude: /node_modules/,
loader: "babel-loader" ,
options:{
"plugins": [["@babel/plugin-transform-runtime",{
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
}
]
}
複製程式碼
由於babel配置的內容較多,官網推薦在根目錄下建立屬於babel的配置檔案.babelrc檔案
由於babel需要配置的內容非常多,我們需要在專案根目錄下建立一個 .babelrc 檔案。 就不需要在 webpack.config.js 中寫 babel 的配置了。 在 .babelrc 中:
{
"plugins": [["@babel/plugin-transform-runtime", {
"corejs": 2,
"helpers": true,
"regenerator": true,
"useESModules": false
}]]
}
複製程式碼
總結
- 在webpack.config.js中
module.exports = {
mode: 'development', //開發環境進行打包,打包的程式碼不會壓縮
devtool: 'cheap-module-eval-source-map',//不帶列資訊,只對業務程式碼進行sourcemap的生成
entry: {//配置入口檔案
main: './src/index.js'
},
devServer: {//配置webpack,開發環境的除錯
contentBase: './dist',//啟動伺服器的目錄
open: true,//開啟新的埠號8080
port: 8080,
hot: true,//開啟熱替換功能
hotOnly: true
},
module: {//對不同的檔案進行打包規則
rules: [{
test: /\.js$/, //對js檔案的babel-loader打包,其配置在.babelrc檔案中
exclude: /node_modules/, //排除在外:在node_modules中的js,babel-loader不生效
loader: 'babel-loader',
}, {
test: /\.(jpg|png|gif)$/,//對圖片進行打包
use: {
loader: 'url-loader',
options: {
name: '[name]_[hash].[ext]',
outputPath: 'images/',
limit: 10240//小於10240以base64的形式進行打包
}
}
}, {
test: /\.(eot|ttf|svg)$/,//字型檔案的打包
use: {
loader: 'file-loader'
}
}, {
test: /\.scss$/,//scss檔案的打包,先用postcss-loader,在用sass-loader進行解析,最後css-loader進行掛載
use: [
'style-loader',
{
loader: 'css-loader',
options: {
importLoaders: 2
}
},
'sass-loader',
'postcss-loader'
]
}, {
test: /\.css$/,//css檔案的打包,沒有sass-loader的解析
use: [
'style-loader',
'css-loader',
'postcss-loader'
]
}]
},
plugins: [
new HtmlWebpackPlugin({
template: 'src/index.html'
}),
new CleanWebpackPlugin(['dist']),//自動清空上一次打包
new webpack.HotModuleReplacementPlugin()//熱替換外掛
],
output: {//出口檔案
filename: '[name].js',
path: path.resolve(__dirname, 'dist')
}
}
複製程式碼