前言
首先,當我們編寫代碼時,不通過模塊化的思想想要引入一個js,通常是在html文件中創建一個script標簽,引入我們需要的js,如果我們引入的是自己手寫的js,很容易發生變量重名的沖突。
例如:
// 變量同名沖突
// a.js
var a = 111 或者 let a = 111
// b.js
var a = 222 或者 let a = 222
// index.html 中同時引入a.js、b.js發生錯誤
所以,我們有時想通過一些方法去避免這些錯誤,比如:匿名函數,但匿名函數又會發生每個js文件中的作用域私有,而代碼不可發生復用的問題,因此又需要想方法解決,例如:
// a.js
var moudleA = (function() {
const obj = {}
const a = 111
obj.a = a
return obj // 將匿名函數中的變量暴露出去,實現代碼復用
})()
// b.js
(function() {
const a = 111
console.log(moudleA.a) // 引用a中的變量,并且解決變量重名
})()
// index.html 中同時引入a.js、b.js
并且,這種導入方式對js插入順序依賴性很強,一般公司多人開發,不同的開發人員引入不同的js文件,插入位置錯誤發生的報錯。
但不可否認,通過匿名函數將變量暴露出去這種方式,就是最基礎的模塊封裝。當然現在對于前端模塊化已經有了很多規范:Common.js、AMD、CMD、ES6中的Modules。
當然,這些東西我這不進行說明了,資料百度就行。
開始
1. 先創建一個項目
打包文件
2.安裝和使用webpack
使用模塊化思想,解決變量重名和代碼打包
當然開發文件中需要安裝依賴和包, 先將文件夾init一下吧。
npm init -y
文件夾是中文名的話,這樣會報錯,需要npm init
回車然后填寫名稱,其他信息一路回車即可,然后熟悉的package.json
兩兄弟文件就在根目錄中出來了。
現在安裝webpack,我使用的是3.6.0, 現在版本4.4.x了,你想跟我一樣 直接npm i webpack@3.6.0 -D
即可。
我npm安裝是縮寫模式, 簡單說下:
-D
為 --save-dev
開發環境中使用
-S
為 --save
生成環境中使用
縮寫不縮寫無所謂, 不差那幾個字母, 我比較懶。
順帶說下為啥在項目中安裝webpack,而不是全局安裝?
當你全局安裝了webpack, 你使用指令
webpack
打包又先調用的是你全局的webpack。 畢竟你今天安裝的webpack是這個版本,不能保證以后誰拿你代碼一運行調用他電腦下載的webpack版本,beng,滿屏飄紅就出來了。
所以保險點,項目都安裝自己的webpack。
2.1 打包
當你安裝好webpack后執行打包指令后, 執行"webpack xxxx"時要注意,此時如果你安裝了全局webpack,任然調用的是你全局的而不是項目自己的,你項目調用自己的webpack指令應該是./node_modules/.bin/webpack xxxxx
這時你會說 “我@#$X,很@#%煩”, 為了解決該問題,我們可以添加webpack.config.js
// webpack.config.js
const path = require("path")
module.exports = {
entry: "./main.js", // 入口
output: {
path: path.resolve(__dirname, 'dist'), // 出口。必須為絕對路徑,所以借助node的path模塊
filename: 'bundle.js' // 打包后文件名
},
}
// 意思就是:我根目錄有個main.js文件,里面導入了好多東西,幫我打包到我目錄里面的dist文件下bundle.js文件里
并且還需要更改一下pachage.json文件中的scripts屬性
{
...
scripts: {
...
"build": "webpack" // build可以隨便取,如: 叫xxx,終端運行 npm run xxx即可
}
...
}
// 意思: 以后我直接在終端運行一個叫build的指令去調用webpack打包, 并且通過該指令, 會自動幫我們調用開發環境中的webpack打包
當運行npm run build
時,webpack會看看我們根目錄是不是有一個叫webpack.config.js的文件,然后讀取運行其中的配置。
弄完這一步,最后將index.html
中引入的main.js
改為bundle.js
后,我們可以愉快的將各個js文件當成模塊,各種require、import、、、,盡情導入導出的去騷了。
比如,我這里在src文件夾中新建js
文件夾,添加info.js
、mainUnit.js
然后再main.js
中引入并使用
// info.js
const name = "zou";
const age = 18;
const height = 18.8;
export default {
name,
age,
height
}
// mainUnit.js
function add(num1, num2) {
return num1 + num2
}
function mul(num1, num2) {
return num1 * num2
}
module.exports = {
add,
mul
}
// main.js
import info from './src/js/info';
const {add, mul} = require('./src/js/mainUnit.js');
console.log(add(100, 200));
console.log(mul(100, 200));
console.log(info);
注意:我結構中webpack.config.js多了copy是因為后期做了抽離,webpack打包并沒有調用我的webpack.config copy.js
loader的使用
webpack并不能識別許多文件類型,我們需要通過各種各樣的loader,來幫webpack識別。
干完上面一步的時候, 在我們騷的還沒盡興完時。
領導一看,“我靠,流批,給我把所有的js相關,css相關,圖片相關都給我弄進去, 以后我只上線你導出的這一個js文件”。
(css隨便添加點東西,less文件中寫些less語法)
這時,在main.js中引入
require("xxx.css")
或者import "xxx.less"
,然后一運行,發現我的終端,好紅啊。
3.1引入css相關loader
進入webpack官網, 點擊導航中的LOADERS,點擊左邊導航的樣式,然后就可以根據需求對照文檔安裝和配置你需要的樣式相關loader了
我這里安裝css和less相關的loader,npm i style-loader css-loader less-loader less -D
因為loader基本用于開發環境, 所以安裝一般都為--save-dev
// webpack.config.js
module.exports = {
...
rules: [
{
test: /\.css$/,
// css-loader 只負責解析css,并不負責插入樣式到頁面
// style-loader 負責將樣式插入頁面中
// 使用多個loader時,從右往左調用
use: ['style-loader','css-loader']
},
{
test: /\.less$/,
use: [{ // use中如需配置其他options 可以使用Object形式, 否則不配置可直接用Array形式,同上css-loader
loader: "style-loader"
}, {
loader: "css-loader"
}, {
loader: "less-loader"
}]
},
]
...
}
好了,css文件的問題解決了,現在繼續解決圖片的問題
3.2引入img相關loader
在網上隨便找2張一大一小圖片,隨便一個引入的css文件中添加,我是在less中添加,順便試一試less-loader,如:
@fontSize: 40px;
@fontColor: orange;
body {
//font-size: @fontSize;
//color: @fontColor;
background-image: url('../img/big.jpg');
background-size: 30%;
}
同樣進入webpack官網, 點擊導航中的LOADERS,找找找,蒙了!沒有img相關的loader啊。那我來告訴你,去看看文件類目中的url-loader
。
話不多說,對著文檔就是一頓安裝,復制
// webpack.config.js
module.exports = {
...
rules: [
...
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
// 當加載圖片,小于limit時,會將圖片編譯成base64字符串形式
// 當加載圖片,大于limit時,需要安裝file-loader進行加載,加載圖片會放入你打包輸出的文件目錄
limit: 8192 // 8192 / 1024 = xKB
}
}]
}
...
]
...
}
limit屬性說明如上方注釋,當圖片小于limit設定的值時以base64字符串形式插入在打包的js中,此時沒問題。
但是,一旦大于設定值,webpack會報錯,需要安裝file-loader
,安裝即可,不需要單獨配置。
然后wenpack會將引入圖片完整打包放入dist
文件夾目錄下,名稱為32位hash值,并將開發環境中引用的圖片路徑替換為打包生成的圖片,而此時index.html引用的為開發目錄中(即img文件夾下)的圖片,所以打包后再運行index.html
發現背景圖片不見了
所以這時我們需要當圖片大于limit設定值時的打包后頁面引入路徑更改為
dist/xxxxx
,好的,進入webpack.config.js,在其中output
屬性中添加publicPath
屬性
// webpack.config.js
module.exports = {
...
output: {
...
// 添加該屬性,以后打包文件所有涉及到url的東西,都會自動在路徑前添加 dist/
publicPath: 'dist/'
}
...
}
好了,現在js模塊、css相關、圖片相關都可以正常被打包了。
我們通常并不希望,所有的img全部打包在dist文件夾下,而是希望放在dist/img文件夾中,并且希望圖片保持原名,但怕重名,還需要加點hash值。
因此,我們可以繼續配置url-loader
規則
// webpack.config.js
module.exports = {
...
rules: [
...
{
test: /\.(png|jpg|gif)$/,
use: [{
loader: 'url-loader',
options: {
limit: 8192 ,
// 當不希望它打包直接放入dist文件中時,可以添加name屬性,
// 當需要保存原名時可以添加[name]
// 當需要防止重名又不需要太長的hash值時可以添加[hash: x] x為你需要hash值位數
name: 'img/[name].[hash:8].[ext]'
}
}]
}
...
]
...
}
好了,打包試試看。
因為之前有publicPath
配置,所以打包運行index.html
頁面圖片引入路徑仍然為dist/img/xxxx.xxx
ES6轉ES5
完成上面一步之后,領導打開他很久沒更新的瀏覽器,發現頁面并沒有出來,打開控制臺一看,全是ES6語法報錯,然后立馬說:“你這打包不對勁啊,瀏覽器版本一低就各種報錯,我們不能保證用戶都使用可以識別ES6語法的瀏覽器,代碼應該要將老舊瀏覽器都兼容啊。”
好吧,滿足他的需求,打開瀏覽器搜索babel
,進入中文官網,點擊設置
選擇Webpack
,對照文檔在編輯器中一頓輸出。
npm install --save-dev babel-loader @babel/core @babel/preset-env
安裝完成后,在webpack.config.js中添加規則:
// webpack.config.js
module.exports = {
...
rules: [
...
{
test: /\.js$/,
exclude: /node_modules/, // 排除的目錄
// 使用babel-loader將ES6代碼轉為ES5,做瀏覽器兼容
// 同時需要建立.babelrc文件,調用@babel/preset-env插件將E6轉為E5S
loader: "babel-loader"
}
...
]
...
}
此時,babel-loader
已經可以將ES6語法識別,但是打包將ES6轉ES5還需要@babel/preset-env
插件,所以我們要新建一個名為.babelrc
的babel配置文件使用該轉譯插件:
// .babelrc
{
"presets": ["@babel/preset-env"]
}
大功告成,圓滿收工!
使用Vue
正準備執行回家程序操作,領導叫住說:“項目前端框架要用vue,就用你那玩意搞,正好可以打包。”得,繼續搭vue吧。
npm i vue -S
因為vue不僅是在開發環境中使用,并且打包后依然需要依賴vue,所有安裝在生成環境中。
安裝之后,在main.js
中使用一下吧
// main.js
import Vue from "vue";
new Vue({
el: "#app",
data: {
msg: "哈哈,使用了Vue"
}
})
然后在index.html
中使用一下
// index.html
...
<div id="app">
<h2>{{ msg }}</h2>
</div>
...
寫完后,覺得干的很漂亮,美滋滋,打包,運行index.html
,dang,在頁面上沒有看到哈哈,打開控制臺一看,一串紅,很煩。
好吧,
main.js
中引入的vue模塊,使用的是runtime-only
版本的,該版本有vue運行代碼,但沒有編譯template
的代碼。那咋辦,換個vue模塊引入的版本唄,打開webpack.config.js。
// webpack.config.js
module.exports = {
...
// 設置模塊如何被解析
resolve: {
// 當安裝vue時,默認使用的是runtime-only版本,此版本只含有vue運行的代碼,不包含編譯template的代碼
// 需要重新更換含有runtime-compiler的版本,因為runtime-complier含有complier代碼可以用于編譯template
// alias(別名): 用別名代替前面的路徑,不是省略,而是用別名代替前面的長路徑
// 如下,當main.js中import Vue from "vue"時,因為vue是別名,所以實際為import Vue from "vue/dist/vue.esm.js"
// 別名好處是webpack直接會去別名對應的目錄去查找模塊,減少了webpack自己去按目錄查找模塊的時間
alias: {
'vue$': 'vue/dist/vue.esm.js'
}
},
...
}
屬性解釋如上圖,語文好的去官方文檔看,反正我是理解不了它說的啥,文檔說點白話,通俗易懂不好嗎。
簡單說alias
就是拼接導入模塊的路徑,emmm,還是舉例吧,如下:
// webpack.config.js
...
alias: {
'vue$': 'vue/dist/vue.esm.js' // 這是別名vue
}
...
// main.js
import Vue from "vue" <==> import Vue from "vue/dist/vue.esm.js"
import Vue from "vue/xxx/xxx" <==> import Vue from "vue/dist/vue.esm.js/xxx/xxx"
好了,應該懂了,反正也是寫我自己看的,你們不理解百度去吧。
在我這其實是將alias
當成vue模塊引入版本重定向,當引入vue,默認引入vue.runtime.common.js文件,而我將引入文件重定向為vue.esm.js。
我怎么知道默認引入哪個版本的?
打開node_modules/vue/package.json,查看其中main屬性,就是vue模塊默認引入的版本。
其他版本,在node_modules/vue/dist文件夾中。
到這一步,vue配置就完成了。
vue中template的封裝
完成上一步已經可以打包運行vue,并且編譯template語法了。上方沒重定向之前,明明沒有template語法為何打包運行會報錯呢?
// vue中template語法之一
// html
...
<div id="app"></div>
<template id="demo">
<div>
這是模板語法之一
</div>
</template>
...
// js
const tmp = {
template: "#demo"
}
Vue.component("cpn", tmp)
是不是感覺很熟悉?可以理解為new vue
中el
屬性通常掛載的"#app"
其實就是實例的模板。
好了,回歸當前正題,一般在使用vue開發過程中,我們會發現通常只有一個html文件,稱之為單頁面復應用(SPA:single-page application),而在SPA中,我們不希望所有的節點全部寫在那一個html文件,html中只需要一個vue掛載節點即可,并且也不希望將所有的js全部寫在new Vue
中,所以需要將代碼進一步的封裝。
下面展示,如何將代碼一步一步抽離:
1. 初始代碼
// index.html
...
<div id="app">
{{ msg }}
<button @click="btnClick">點擊</button>
</div>
...
// main.js
...
new Vue({
el: "#app",
data: {
msg: "哈哈,使用了Vue"
},
methods: {
btnClick() {
console.log('vue內容封裝');
}
}
})
...
1.1封裝一
// index.html
...
<div id="app"></div>
...
// main.js
...
new Vue({
el: "#app",
template: `
<div id="app">
{{ msg }}
<button @click="btnClick">點擊</button>
</div>
`,
data: {
msg: "哈哈,使用了Vue"
},
methods: {
btnClick() {
console.log('vue內容封裝');
}
}
})
...
1.2封裝二
// main.js
...
new Vue({
el: "#app",
template: '<App/>',
components: {
App: {
template: `
<div id="app">
{{ msg }}
<button @click="btnClick">點擊</button>
</div>
`,
data() {
return {
msg: "哈哈,使用了Vue"
}
},
methods: {
btnClick() {
console.log('vue內容封裝');
}
}
}
}
})
...
1.3封裝三
利用模板化思想,將組件抽離放入一個js文件中,將其暴露出來,在src文件夾新建vue文件夾中新建app.js
// app.js
export default {
template: `
<div id="app">
{{ msg }}
<button @click="btnClick">點擊</button>
</div>
`,
data() {
return {
msg: "哈哈,使用了Vue"
}
},
methods: {
btnClick() {
console.log('vue內容封裝');
}
}
}
// main.js
import App from './src/vue/app.js';
new Vue({
el: "#app",
template: '<App/>',
components: {
App
}
})
做完這一步,項目已經實現模塊化思想,但是此時封裝缺點是模板(即節點)沒有和js分離,這時候需要使用.vue
文件做進一步封裝
1.4封裝四
在vue文件夾中創建App.vue,刪除app.js(也可以保留,只有main.js里沒引用就行)
// App.vue
<template>
<div id="app">
{{ msg }}
{{ devServer }}
<button @click="btnClick">點擊</button>
</div>
</template>
<script>
export default {
name: "App",
data() {
return {
msg: 'hello Webpack!',
devServer: '測試webpack-dev-server插件是否生效'
}
},
methods: {
btnClick() {
console.log('vue內容封裝--App.vue');
}
}
}
</script>
<style lang="scss" scoped>
</style>
將main.js中導入app.js修改成導入App.vue
// main.js
// import App from './src/vue/app.js';
import App from './src/vue/App.vue';
此時,打包項目,發現終端報錯,因為webpack并不能識別.vue
文件,所以需要安裝對應的loader
來幫助webpack識別.vue
文件。
npm i vue-loader vue-template-compiler -D
在webpack.config.js中配置loader規則:
// webpack.config.js
const VueLoaderPlugin = require('vue-loader/lib/plugin');
module.exports = {
...
module: {
rules: [
...
{
test: /\.vue$/,
loader: 'vue-loader'
}
...
]
},
plugins: [
new VueLoaderPlugin() // 因為我安裝的"vue-loader"版本為"^15.7.2",需要配置此插件
]
}
...
做到這里,到時候,是不是發現已經很像vue-cli了,只剩下使用一些配置插件了。
插件的使用
如果說loader是webpack的識別器的話,那么插件(plugin)就可以說是webpack的拓展器,幫助我們延伸webpack的功能
html-webpack-plugin插件
當封裝之后,發現我們webpack打包之后并沒有生成index.html文件,而我們需要生成html文件,所以應該安裝html-webpack-plugin插件。
- html-webpack-plugin插件 可以幫我們在打包文件生成html文件 并且自動生成script標簽引入我們打包的js文件
- 并且會將頁面所有引入的相關鏈接自動指向輸出的文件夾目錄下問生成文件
先來安裝該插件npm i html-webpack-plugin -D
,然后再到webpack.config.js中繼續配置
// webpack.config.js
...
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = {
...
output: {
...
// publicPath: 'dist/'
},
plugins: [
...
new HtmlWebpackPlugin({
template: 'index.html' // 指定生成html的模板,如果不知道只會默認生成html文件插入打包后js,并沒有<div di="app"></div>節點
})
]
...
}
配置此插件后就可以將其中output
的publicPath
屬性注釋或刪除,并且index.html
中script標簽引入bundle.js
這段也可以刪除了。
uglifyjs-webpack-plugin插件
這個插件可以幫助我們壓縮和丑化打包的js代碼,當然js壓縮不壓縮看自己需求。
為什么不直接用webpack打包是因為vue-cli2中使用該插件打包并沒有使用webpack自帶的打包,安裝此插件版本不用安裝最新,這里只做演示所以安裝的為1.1.1版本,安裝最新版本會報錯,需要重新查看使用文檔。
先安裝,npm i uglifyjs-webpack-plugin@1.1.1 -D
,然后到webpack.config.js中配置
// webpack.config.js
...
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
module.exports = {
...
plugins: [
...
new UglifyjsWebpackPlugin(), // 壓縮 丑化打包后的js
]
...
}
BannerPlugin插件
當時需要對打包文件添加版權聲明時(當然對我們國家來說,你開源的就是大家的),可以使用webpack自帶的BannerPlugin插件。
在國外,對版權還是很重視的,所以大公司開源的代碼大多攜帶版權聲明,和版權協議類型。
其中vue使用的是MIT協議,此協議代表作者只想保留版權,而無任何其他了限制。
曾經的react的協議并不是MIT而是BSD,該協議就是說,如果你公司以react為核心構建了項目,當有一天Facebook(react是Facebook的)公司要對你公司干點什么的話,比如下架產品,或者收費什么的,那你公司就麻煩了,具體自己可以百度查查,所以當初很多公司項目開始想用react但看到協議都放棄了,比如:百度。
題外話不好了,來使用一波BannerPlugin
// webpack.config.js
...
const webpack = require('webpack');
module.exports = {
...
plugins: [
...
new webpack.BannerPlugin('這是在js壓縮后添加的版權聲明')
]
...
}
注意:如果同時使用了uglifyjs-webpack-plugin和BannerPlugin,將uglifyjs-webpack-plugin放在BannerPlugin之前調用,防止把你的聲明也給丑化了,那要聲明干嘛。
webpack-dev-server(熱更新)
webpack提供一個可選本地開發服務器,這個本地服務器基于node.js搭建,內部使用express框架,可以實現讓瀏覽器自動刷新顯示我們修改后的結果
弄完上面那些東西,是不是覺得每次修改代碼都需要打包再運行很煩,現在,它(熱更新)來了。
先安裝npm i webpack-dev-server -D
,老規矩,在webpack.config.js中配置
// webpack.config.js
...
module.exports = {
...
output: {...},
devServer: {
contentBase: './dist', // 為哪一個文件夾提供服務,默認根目錄
inline: true // 頁面實時刷新
// port: 8080, // 端口號,默認8080
// 其他配置自行看文檔
},
...
}
想要使用它,還需要修改項目中的package.json,在其script
屬性中添加運行指令(也可以不加,直接在終端寫路徑去調用,同之前webpack調用)
// package.json
...
"scripts": {
"dev": "webpack-dev-server"
}
...
然后,在終端中運行指令npm run dev
,之后就可以實時查看修改代碼而不用頻繁去打包運行了。
webpack.config.js抽離 和 webpack-merge
在我們安裝的依賴當中,有些是開發和生產時需要的依賴,有些是開發時的依賴,還有些是生產時的依賴,我們不希望每次都運行所有的依賴,所以需要對webpack.config.js進行抽離封裝
在根目錄新建build
文件夾,新增三個js文件,如下:
-
base.config.js
為通用配置文件 -
dev.config.js
為開發配置文件 -
prod.config.js
為生產配置文件
此時目錄結構
新增文件之后
- 將
webpack.config.js
中的代碼復制到base.config.js
中 - 將
webpack.config.js
中開發需要的配置抽取到dev.config.js
中 - 將
webpack.config.js
中開發需要的配置抽取到prod.config.js
中 - 刪除
base.config.js
中和dev.config.js
、prod.config.js
文件重復的代碼 - 刪除
webpack.config.js
,也可以改個名讓webpack不認識它,像我就改成webpack.config copy.js
- 安裝
npm i webpack-merge -D
,幫助我們將不同環境中的配置和通用配置文件合并 - 修改
package.json
指令
// dev.config.js
const webpackBase = require("webpack-merge"); // 合并模板
const baseConfig = require("./base.config"); // 導入共同配置
module.exports = webpackBase(baseConfig, {
devServer: {
contentBase: '../dist',
inline: true
}
})
// prod.config.js
// 開發調試時需要將 共同配置合并開發配置
const webpackBase = require("webpack-merge"); // 合并模板
const baseConfig = require("./base.config"); // 導入共同配置
const UglifyjsWebpackPlugin = require('uglifyjs-webpack-plugin');
const webpack = require('webpack');
module.exports = webpackBase(baseConfig, {
plugins: [
new UglifyjsWebpackPlugin(),
new webpack.BannerPlugin('這是在js壓縮后添加的版權聲明')
]
})
// base.config.js 復制webpack.config.js代碼并刪除以上配置
// package.json
...
"scripts": {
"build": "webpack --config ./build/prod.config.js",
"dev": "webpack-dev-server --config ./build/dev.config.js"
}
...
自此,一個簡單的vue腳手架就干完了!