漸進式配置webpack4單頁面和多頁面
前言
使用包的版本
webpack ->4.3.0
babel-loader ->8.0.5
npm ->6.4.1
webpack-cli ->3.3.1
複製程式碼
每個章節對應一個demo
一、初始化專案
進入專案目錄,執行 npm init來建立專案
npm init
複製程式碼
終端輸入完成後會自動建立一個package.json的檔案。
{
"name": "demo1",
"version": "1.0.0",
"description": "webpack-demo",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "webpack app.js"
},
"keywords": [
"demo"
],
"author": "cmf",
"license": "ISC",
"devDependencies": {}
}
複製程式碼
執行命令,引入webpack和webpack-cli,安裝webpack-cli是為了在專案裡面執行webpack命令。
npm i webpack webpack-cli -D
複製程式碼
手動建立webpack.config.js。
touch webpack.config.js
複製程式碼
在終端裡面通過npx執行webpack命令無法進行復雜配置。所以建立webpack.config.js是為了後面的複雜配置。 新建app.js、index.html、view資料夾、view資料夾裡面建立dom.js。
mkdir view
touch app.js
cd view
touch app.js
複製程式碼
程式碼內容詳見demo1 配置 webpack.config.js
打包方式有兩種- 通過npx命令
npx webpack
複製程式碼
- 通過配置package.json的script物件。
"scripts": {
"build": "webpack"
},
複製程式碼
這兩種方式都會自動尋找專案裡面的webpack包進行打包,並且webpack會根據webpack.config.js的配置規則進行打包。
二、配置開發和生產環境
- 開發環境特點
- 視覺化
- 本地服務
- 可以快速找到程式碼錯誤
- 熱更新
- 生產環境特點
- 程式碼壓縮
- 程式碼拆包
- 快速響應
下載外掛包
npm i cross-env html-webpack-plugin webpack-dev-server -D
複製程式碼
cross-env 跨平臺的解決了環境變數和引數的命令配置。
html-webpack-plugin 打包生成HTML的外掛。
webpack-dev-server建立開發伺服器。功能強大,介面轉發、熱更新等。
檔案更改
新建index.html、help.js
touch index.html
touch help.js
複製程式碼
index.html 模板檔案
<head>
<title>demo2</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
</head>
<body>
<div id="app"></div>
</body>
複製程式碼
help.js 封裝的一些方法。
module.exports.getMode = function() {
return process.env.NODE_ENV === 'development'?'development':'production'
};
複製程式碼
配置更改
webpack-dev-server其他配置請參考官方文件。
mode模式。預設值是production。
告知 webpack 使用相應模式的內建優化。是 webpack4新增的屬性。比如mode是production,webpack會預設新增一些打包外掛比如:NoEmitOnErrorsPlugin。可以節省很多配置。
命令配置
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build": "cross-env NODE_ENV=production webpack",
"dev": "cross-env NODE_ENV=development webpack-dev-server"
},
複製程式碼
cross-env NODE_ENV=production 設定環境變數資訊
npm run dev
複製程式碼
自動開啟瀏覽器,會把app.js裡面的內容自動注入到index.html裡面。
三、模板解析與外部擴充套件
程式碼內容詳見demo3
外部擴充套件(externals)
externals防止將某些 import 的包是從外部獲取依賴不是從node_modules裡面獲取的。
例如我們在app.js裡面
// app.js
import Vue from 'Vue';
複製程式碼
index.html
<head>
<title>demo4</title>
<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.10/dist/vue.js"></script>
</head>
複製程式碼
webpack.config.js
externals:{ //外部擴充套件
'Vue':'window.Vue'
},
複製程式碼
現在app.js 裡面引入的Vue是從cdn裡面引入的,而不是從node_modules包裡面引入的。 這樣做的好處:
- 提高打包效率。
- 類似於使用Vue這樣的框架,使用的版本是一般是固定不變的。所以cdn引入的時候相當於模組化拆包。使用者重新整理瀏覽器和程式碼重新打包發版都不會再次請求Vue的程式碼,提高使用者體驗。
模板解析(resolve)
resolve開發者可以自定義解析規則。
-
resolve.modules 自定義依賴包的路徑。引數是array,可以是相對目錄也可以是絕對目錄。預設是 [path.resolve(process.cwd(),'node_modules')]。當前命令的目錄下面的node_modules資料夾。
如果一個大的專案裡面有很多子專案,每個子專案沒必要都安裝依賴包,所以可以在各個子專案裡面通過配置resolve.modules指向母專案的node_modules包。 -
resolve.modules 自定義引入模組時可以不帶副檔名。引數是array。預設['.js','.json']
extensions:['.js','.vue','.json','.css','.less'], 複製程式碼
程式碼裡面
import 'view/dom' 複製程式碼
會自動找到dom.js
-
resolve.alias 自定義路徑別名。引數是object。
alias:{ '@': path.resolve(__dirname, './src') }, // __dirname 當前檔案的目錄地址。 複製程式碼
在程式碼裡可以使用@符號指向src目錄。
import '@/view/dom.js'; 複製程式碼
好處:在很深的程式碼層裡面如果想引入頂層的某個js檔案。不需要寫很多'../../../'去找檔案,提高開發效率。
resolve裡面有很多的自定義解析規則。有時間都可以嘗試一下。
四、生產與開發環境分開打包
程式碼內容詳見demo4
雖然在webpack4裡面提供了mode屬性來分別打包開發和生產環境,各自提供不同的外掛。但是如果clean-webpack-plugin外掛想在生產環境使用在開發環境不使用,就需要每次手動更改配置,這樣做很不合理,容易出錯。
可以使用
- 指定配置檔案。
webpack --config 配置檔案 複製程式碼
- 合併公共的配置webpack-merge
具體操作
新增依賴包
npm i webpack-merge -D
複製程式碼
新增檔案
新建build資料夾
mkdir build
cd build
touch weboack.build.conf.js、webpack.base.conf.js、webpack.dev.conf.js config.js help.js
複製程式碼
help.js 儲層公共方法
var path = require('path');
module.exports.getMode = function() {
return process.env.NODE_ENV === 'development'?'development':'production'
};
module.exports.resolve = function(p){
return path.resolve(process.cwd(),p);
}
複製程式碼
config.js 開發與生產的配置資訊
module.exports = {
dev: {
mode: 'development',
publicPath: '/',
devServer: {
port: '8899',
proxy: {
'/test/shortRent': {
target: 'http:"//www.baidu.com',
changeOrigin: true,
pathRewrite: {
'^/test/shortRent': '/evcard-evrental'
}
},
},
},
},
build: {
mode: 'production',
publicPath: './',
assetsRoot: 'you-app'
}
}
複製程式碼
webpack.base.conf.js 開發與生產相同的webpack配置
var path = require('path');
var help = require('./help.js');
var config = require('./config.js');
var htmlWebpackPlugin = require('html-webpack-plugin');
var mode = help.getMode();
module.exports={
entry:{
app:help.resolve('./app.js')
},
output:{
},
resolve:{ // 解析
alias:{
'@': help.resolve('./src')
},
extensions:['.js','.vue','.json','.css','.less'],
modules: ["./node_modules"]
},
externals:{ //外部擴充套件
'Vue':'window.Vue'
},
plugins:[
new htmlWebpackPlugin({
filename:'index.html',
template:'./index.html',
inject:true,
})
],
}
複製程式碼
webpack.dev.conf.js 開發環境的webpack配置
var merge = require("webpack-merge");
var webpackConfigBase = require('./webpack.base.conf');
var help = require('./help.js');
var config = require('./config.js');
module.exports=merge(webpackConfigBase,{
mode: config.dev.mode,
output:{
filename:help.assetsPath('js/[name].js'),
publicPath:config.dev.publicPath
},
devServer:config.dev.devServer
})
複製程式碼
webpack.build.conf.js 生產環境的webpack配置
var cleanWebpackPlugin = require('clean-webpack-plugin');
var merge = require("webpack-merge");
var help = require('./help.js');
var webpackConfigBase = require('./webpack.base.conf.js');
var config = require('./config.js');
module.exports=merge(webpackConfigBase,{
mode: config.build.mode,
output:{
filename:'assets/js/[name].[hash].js',
publicPath:config.build.publicPath,
path:help.resolve(config.build.assetsRoot),
},
plugins:[
new cleanWebpackPlugin()
]
})
複製程式碼
命令修改
"scripts": {
"build": "cross-env NODE_ENV=production webpack --config build/weboack.build.conf.js",
"dev": "cross-env NODE_ENV=development webpack-dev-server --config build/webpack.dev.conf.js"
},
複製程式碼
五、解析轉譯JS
程式碼內容詳見demo5
loaders
loader 用來解析檔案轉譯成瀏覽器可以識別的檔案。如.less、.vue、.jsx等這些檔案瀏覽器是不能正常轉譯的,loaders的作用就是充當著'翻譯'的作用。
babel-loader
我們在開發的時候都使用es6的語法去編寫程式碼,但是有些瀏覽器不支援es6的程式碼就需要將es6轉譯成瀏覽器可以讀懂的es5的程式碼。babel-loader的作用就是'翻譯'es6程式碼。
基礎配置
安裝babel-loader。參照官方的安裝方式,開啟官網選擇webpack的安裝方式。
安裝依賴
npm install --save-dev babel-loader @babel/core
npm install @babel/preset-env --save-dev
複製程式碼
babel-loader @babel/core 是核心外掛
preset-env 編譯方式
配置規則
module: {
rules: [
{
test: /\.js$/, // 正則匹配,所有的.js檔案都使用這個規則進行編譯
exclude: /node_modules/, // 排除的資料夾。這個資料夾裡面的的檔案不進行轉譯
loader: "babel-loader", // 轉譯的外掛
options: { // 轉譯的規則
presets: [ //轉譯器配置
[
"@babel/preset-env"
]
],
plugins: [] // 轉譯外掛配置
}
},
]
}
複製程式碼
plugins(轉譯外掛)。轉譯外掛是用來轉譯單一功能的外掛,比如transform-es2015-arrow-functions,這個外掛只負責轉譯es2015新增的箭頭函式。
presets(轉譯器)。轉譯器是一系列轉譯外掛的集合。比如babel-preset-es2015就包含了es2015新增語法的所有轉譯外掛,比如包含transform-es2015-arrow-functions(es2015箭頭函式轉譯外掛)、transform-es2015-classes(es2015 class類轉譯外掛)等。轉譯器分為語法轉譯器和補丁轉譯器。 詳解
在app.js裡面寫es6的程式碼
const s = new Set([1, 2, 3, 4, 5, 3, 2, 16, 7, 83, 21, 2, 1]);
var w = Object.assign({}, { w: 1, e: 4 })
console.log(w);
console.log([...s]);
function pro(v) {
return new Promise((resolve) => {
if (v) {
resolve('真11')
} else {
resolve('假22')
}
})
}
pro(true).then(res=>{
console.log(res)
})
複製程式碼
執行命令
npm run build
複製程式碼
開啟打包後的app.js只有1.7kb。
promise裡面的箭頭函式已經被轉譯成了普通函式。似乎看起來已經已經成功了。
npm run dev
複製程式碼
在打包後的js全域性搜尋promose,程式碼裡面只有一處。沒有其他的程式碼來'翻譯'promise,這在低版本的瀏覽器裡面是不行的,所以需要繼續'翻譯'。
墊片配置
Babel 預設只轉換新的 JavaScript 句法(syntax),而不轉換新的 API,比如 Iterator、Generator、Set、Maps、Proxy、Reflect、Symbol、Promise 等全域性物件,以及一些定義在全域性物件上的方法(比如 Object.assign)都不會轉碼。
所以還需要配置。
在babel@7.4以前使用@babel/polyfill為當前環境提供一個墊片。所謂墊片也就是墊平不同瀏覽器或者不同環境下的差異。
但是babel@7.4及以後這個墊片被廢棄了。
官方建議使用
import "core-js/stable";
import "regenerator-runtime/runtime";
複製程式碼
來代替@babel/polyfill。
- regenerator:提供對 generator 支援,如果應用程式碼中用到generator、async函式的話。
- core-js:提供 es(6-8) 新的特性。
core-js 在安裝@babel/preset-env的時候已經安裝好了。
npm i regenerator-runtime -D
複製程式碼
在app.js裡面引入
import "core-js/stable";
import "regenerator-runtime/runtime";
複製程式碼
執行命令
npm run build
複製程式碼
打包後的app.js有113kb。多出來了100多kb的程式碼。開啟打包後的js,搜尋promise 一共有26處,這裡面對promise的程式碼進行了'翻譯'。
但程式碼相比以前也太大了。把所有轉譯es的程式碼都打包了。
優化
useBuiltIns
這個配置屬性可以解決這個問題。官方文件的配置其實是babel@7.4以前的寫法,這也造成了我懵逼了兩天。
屬性值 "usage" | "entry" | false, 預設是 false。
useBuiltIns: 'entry' 其實和import "core-js/stable"; import "regenerator-runtime/runtime";效果是一樣的,表示把所有的轉譯程式碼都注入到打包的程式碼裡面。但是還是需要在程式碼裡面引入這兩個外掛。
useBuiltIns: 'usage' 表示把程式碼裡面需要用到的轉譯程式碼注入到打包的程式碼裡面。就不需要引入core-js/stable了。但是regenerator-runtime/runtime還是需要繼續引入。
{
test: /\.js$/, // 正則匹配,所有的.js檔案都使用這個規則進行編譯
exclude: /node_modules/, // 排除的資料夾。這個資料夾裡面的的檔案不進行轉譯
loader: "babel-loader", // 轉譯的外掛
options: { // 轉譯的規則
presets: [ //轉譯器配置
[
"@babel/preset-env", {
useBuiltIns: "usage"
}
]
],
plugins: [] // 轉譯外掛配置
}
},
複製程式碼
執行打包命令
npm run build
複製程式碼
打包報錯了
根據preset-env 的文件說明還需要配置corejs。作用是代替引入core-js/stable。{
test: /\.js$/, // 正則匹配,所有的.js檔案都使用這個規則進行編譯
exclude: /node_modules/, // 排除的資料夾。這個資料夾裡面的的檔案不進行轉譯
loader: "babel-loader", // 轉譯的外掛
options: { // 轉譯的規則
presets: [ //轉譯器配置
[
"@babel/preset-env", {
useBuiltIns: "usage",
corejs: 3
}
]
],
plugins: [] // 轉譯外掛配置
}
},
複製程式碼
執行打包命令
npm run build
複製程式碼
只有33kb。
打包的檔案中有21個promise。
這樣做的好處就是使用哪種es語法就引入哪種轉譯器,避免程式碼過大。為什麼還需要繼續引入regenerator-runtime/runtime呢?因為它的程式碼太少了,@babel/preset-env沒有像corejs一樣進行配置。如果不引入async、await就不能使用了。
babel-loader還有很多配置很多坑,遇到就查文件或者google吧。
六、樣式loader與樣式HMR
程式碼內容詳見demo6
樣式loader
樣式loader,這些都是官方提供的樣式loader。
- style-loader 將js裡面引入的css檔案,解析成css樣式並且新增到style標籤裡面。
- css-loader 解析css裡面的@import引入的樣式。
- less-loader sass-loader stylus-loader 都是解析css擴充套件語言。
- postcss-loader 為css3的程式碼自動新增字首。
基礎配置
比如 使用less作為樣式語法。 首先安裝style-loader css-loader。
npm i style-loader css-loader -D
複製程式碼
less-loader文件裡面顯示需要安裝
npm install --save-dev less-loader less
複製程式碼
按照官方的配置進行rules配置。編寫base.less檔案
body{
color: lawngreen;
}
.logo{
background: #f60;
height: 400px;
width: 400px;
background-repeat: no-repeat;
transition: all 1s;
display: flex;
}
.logo:hover{
height: 600px;
width: 600px;
transform: translateY(60px);
}
複製程式碼
在app.js 裡面引入
import './src/assets/css/base.less';
複製程式碼
執行命令
npm run dev
複製程式碼
自動新增css3瀏覽器字首
less檔案裡面的transition和transform都是css3的樣式,如果想自動的生成帶字首的程式碼則需要postcss-loader,也需要postcss-loader的一個外掛autoprefixer
安裝依賴
npm i autoprefixer postcss-loader -D
複製程式碼
更改配置
{
loader: "postcss-loader",
options: {
plugins: [
require("autoprefixer")({
browsers: [
'last 10 Chrome versions',
'last 5 Firefox versions',
'Safari >= 6',
'ie> 8'
]
})
]
}
},
複製程式碼
browsers配置表示按照 大於ie8,Safari6,最後10個Chrome版本的規則進行編譯。
npm run dev
複製程式碼
css3的程式碼都進行了字首編碼。
importLoaders
home.less裡面的程式碼並沒有進行css3的轉化。這時我們需要配置在 css-loader 中使用 importLoaders 屬性。
將importLoaders設定為2因為,css-loader前面需要執行postcss-loader和less-loader。生產環境配置
之前一直執行的是開發環境,我們執行一下生產打包命令
npm run build
複製程式碼
打包後並沒有css檔案。 把程式碼放入伺服器或者使用http-server起一個本地的伺服器。
這時我們需要引入mini-css-extract-plugin進行css程式碼的抽離,以及打包外掛optimize-css-assets-webpack-plugin在打包的時候進行css程式碼壓縮。
npm i mini-css-extract-plugin optimize-css-assets-webpack-plugin -D
複製程式碼
按照官方的語法進行配置
npm run build
複製程式碼
css的程式碼已經被抽離了並且壓縮了。
樣式HMR
檢視官網有關於HMR解釋。
模組熱替換(HMR - Hot Module Replacement)功能會在應用程式執行過程中替換、新增或刪除模組,而無需重新載入整個頁面。
按照官方的例項。 首先更改devServer的配置,設定hot屬性為true。
因為設定模組熱載入是開發環境需要的。所以更改webpack.dev.conf.js的配置。
plugins:[
new webpack.HotModuleReplacementPlugin()
]
複製程式碼
由於mini-css-extract-plugin不支援HMR,所以在開發環境繼續使用style-loader。 現在css的HMR已經配置好了。
檔案解析。
程式碼內容詳見demo7
檔案解析需要用到的loaders。
{
test: /\.(png|jpg|gif)$/,
use: [{
// 需要下載file-loader和url-loader
loader: "url-loader",
options: {
limit: 5 * 1024, //小於5kb將會已base64點陣圖片打包處理
// 圖片檔案輸出的資料夾
outputPath: help.assetsPath("images"),
name: '[name].[ext]'
}
}]
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
outputPath: help.assetsPath("fonts")
}
},
複製程式碼