├── dist 打包輸出目錄, 只需部署這個(gè)目錄到生產(chǎn)環(huán)境
├── package.json 項(xiàng)目配置信息
├── node_modules npm安裝的依賴包都在這里面
├── src 我們的源代碼
│ ├── components 可以復(fù)用的模塊放在這里面
│ ├── index.html 入口html
│ ├── index.js 入口js
│ ├── libs 不在npm和git上的庫扔這里
│ └── views 頁面放這里
└── webpack.config.js webpack 配置文件
1. npm init
生成一個(gè)默認(rèn)的項(xiàng)目配置文件 package.json.
2. 安裝 eslint, 用來檢查語法報(bào)錯(cuò)
npm install eslint eslint-config-enough eslint-loader --save-dev
npm install
可以一條命令同時(shí)安裝多個(gè)包, 包之間用空格分隔. 包會(huì)被安裝進(jìn) node_modules 目錄中芯砸。
--save-dev
會(huì)把安裝的包和版本號(hào)記錄到 package.json 中的 devDependencies 對(duì)象中, 還有一個(gè) --save
, 會(huì)記錄到 dependencies 對(duì)象中, 它們的區(qū)別, 我們可以先簡(jiǎn)單的理解為打包工具和測(cè)試工具用到的包弯菊。
eslint-config-enough 是配置文件, 它規(guī)定了代碼規(guī)范, 要使它生效, 我們要在package.json中添加內(nèi)容:
{
"eslintConfig": {
"extends": "enough",
"env": {
"browser": true,
"node": true
}
}
}
( 注:因?yàn)橛行?npm 包安裝是需要編譯的, 那么導(dǎo)致 windows/mac/linux 上編譯出的可執(zhí)行文件是不同的, 也就是無法通用, 因此我們?cè)谔峤淮a到 git 上去的時(shí)候, 一般都會(huì)在 .gitignore 里指定忽略 node_modules 目錄和里面的文件, 這樣其他人從 git 上拉下來的項(xiàng)目是沒有 node_modules 目錄的 )
3. npm install
讀取 package.json 中的 devDependencies 和 dependencies 字段, 把記錄的包的相應(yīng)版本下載下來阔馋。
4. 建立 src/index.html 文件.
注意這里我們不需要自己寫<script src="index.js"></script>, 因?yàn)榇虬蟮奈募吐窂娇赡軙?huì)變, 所以我們用webpack插件幫我們自動(dòng)加上.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
</body>
</html>
5. 建立 src/index.js 文件.
// 引入作為全局對(duì)象儲(chǔ)存空間的global.js, js文件可以省略后綴
import g from './global'
// 引入頁面文件
import foo from './views/foo'
import bar from './views/bar'
const routes = {
'/foo': foo,
'/bar': bar
}
// Router類, 用來控制頁面根據(jù)當(dāng)前URL切換
class Router {
start() {
// 點(diǎn)擊瀏覽器后退/前進(jìn)按鈕時(shí)會(huì)觸發(fā)window.onpopstate事件, 我們?cè)谶@時(shí)切換到相應(yīng)頁面
// https://developer.mozilla.org/en-US/docs/Web/Events/popstate
window.addEventListener('popstate', () => {
this.load(location.pathname)
})
// 打開頁面時(shí)加載當(dāng)前頁面
this.load(location.pathname)
}
// 前往path, 會(huì)變更地址欄URL, 并加載相應(yīng)頁面
go(path) {
// 變更地址欄URL
history.pushState({}, '', path)
// 加載頁面
this.load(path)
}
// 加載path路徑的頁面
load(path) {
// 創(chuàng)建頁面實(shí)例
const view = new routes[path]()
// 調(diào)用頁面方法, 把頁面加載到document.body中
view.mount(document.body)
}
}
// new一個(gè)路由對(duì)象, 賦值為g.router, 這樣我們?cè)谄渌鹙s文件中可以引用到
g.router = new Router()
// 啟動(dòng)
g.router.start()
在webpack配置后蓄氧,當(dāng)我們?cè)L問http://localhost:8100/foo的時(shí)候, 路由會(huì)加載 ./views/foo/index.js文件
// 引入全局對(duì)象
import g from '../../global'
// 引入html模板, 會(huì)被作為字符串引入
import template from './index.html'
// 引入css, 會(huì)生成<style>塊插入到<head>頭中
import './style.css'
// 導(dǎo)出類
export default class {
mount(container) {
document.title = 'foo'
container.innerHTML = template
container.querySelector('.foo__gobar').addEventListener('click', () => {
// 調(diào)用router.go方法加載 /bar 頁面
g.router.go('/bar')
})
}
}
借助webpack插件, 我們可以import html, css等其他格式的文件, 文本類的文件會(huì)被儲(chǔ)存為變量打包進(jìn)js文件, 其他二進(jìn)制類的文件, 比如圖片, 可以自己配置, 小圖片作為Data URI打包進(jìn)js文件, 大文件打包為單獨(dú)文件.
6. 安裝webpack和Babel
npm install webpack webpack-dev-server html-webpack-plugin html-loader css-loader style-loader file-loader url-loader --save-dev
webpack-dev-server是webpack提供的用來開發(fā)調(diào)試的服務(wù)器, 讓你可以用 http://127.0.0.1:8080/ 這樣的url打開頁面來調(diào)試, 有了它就不用配置nginx了, 方便很多.
html-webpack-plugin, html-loader,css-loader,style-loader等看名字就知道是打包html文件, css文件的插件, 大家在這里可能會(huì)有疑問, html-webpack-plugin
和html-loader
有什么區(qū)別, css-loader和style-loader有什么區(qū)別, 我們等會(huì)看配置文件的時(shí)候再講.
file-loader和url-loader是打包二進(jìn)制文件的插件, 具體也在配置文件章節(jié)講解.
接下來, 為了能讓不支持ES6的瀏覽器(比如IE)也能照常運(yùn)行, 我們需要安裝babel, 它會(huì)把我們寫的ES6源代碼轉(zhuǎn)化成ES5, 這樣我們?cè)创a寫ES6, 打包時(shí)生成 ES5.
npm install babel-core babel-preset-env babel-loader --save-dev
這里babel-core顧名思義是babel的核心編譯器.babel-preset-env是一個(gè)配置文件, 我們可以使用這個(gè)配置文件轉(zhuǎn)換ES2015/ES2016/ES2017到ES5.
但是光安裝了babel-preset-env, 在打包時(shí)是不會(huì)生效的, 需要在package.json加入babel 配置:
{
"babel": {
"presets": [
"env"
]
}
}
打包時(shí)babel會(huì)讀取package.json中babel字段的內(nèi)容, 然后執(zhí)行相應(yīng)的轉(zhuǎn)換.
7. 配置webpack
創(chuàng)建webpack配置文件webpack.config.js, 注意這個(gè)文件是在node.js中運(yùn)行的, 因此不支持ES6的import語法.
const { resolve } = require('path')
const HtmlWebpackPlugin = require('html-webpack-plugin')
module.exports = {
// 配置頁面入口js文件
entry: './src/index.js',
// 配置打包輸出相關(guān)
output: {
// 打包輸出目錄
path: resolve(__dirname, 'dist'),
// 入口js的打包輸出文件名
filename: 'index.js'
},
module: {
/*
配置各種類型文件的加載器, 稱之為loader
webpack當(dāng)遇到import ... 時(shí), 會(huì)調(diào)用這里配置的loader對(duì)引用的文件進(jìn)行編譯
*/
rules: [
{
/*
使用babel編譯ES6/ES7/ES8為ES5代碼
使用正則表達(dá)式匹配后綴名為.js的文件
*/
test: /\.js$/,
// 排除node_modules目錄下的文件, npm安裝的包不需要編譯
exclude: /node_modules/,
/*
use指定該文件的loader, 值可以是字符串或者數(shù)組.
這里先使用eslint-loader處理, 返回的結(jié)果交給babel-loader處理. loader的處理順序是從最后一個(gè)到第一個(gè).
eslint-loader用來檢查代碼, 如果有錯(cuò)誤, 編譯的時(shí)候會(huì)報(bào)錯(cuò).
babel-loader用來編譯js文件.
*/
use: ['babel-loader', 'eslint-loader']
},
{
// 匹配.html文件
test: /\.html$/,
/*
使用html-loader, 將html內(nèi)容存為js字符串, 比如當(dāng)遇到
import htmlString from './template.html'
template.html的文件內(nèi)容會(huì)被轉(zhuǎn)成一個(gè)js字符串, 合并到j(luò)s文件里.
*/
use: 'html-loader'
},
{
// 匹配.css文件
test: /\.css$/,
/*
先使用css-loader處理, 返回的結(jié)果交給style-loader處理.
css-loader將css內(nèi)容存為js字符串, 并且會(huì)把background, @font-face等引用的圖片,
字體文件交給指定的loader打包, 類似上面的html-loader, 用什么loader同樣在loaders對(duì)象中定義, 等會(huì)下面就會(huì)看到.
*/
use: ['style-loader', 'css-loader']
},
{
/*
匹配各種格式的圖片和字體文件
上面html-loader會(huì)把html中<img>標(biāo)簽的圖片解析出來, 文件名匹配到這里的test的正則表達(dá)式,
css-loader引用的圖片和字體同樣會(huì)匹配到這里的test條件
*/
test: /\.(png|jpg|jpeg|gif|eot|ttf|woff|woff2|svg|svgz)(\?.+)?$/,
/*
使用url-loader, 它接受一個(gè)limit參數(shù), 單位為字節(jié)(byte)
當(dāng)文件體積小于limit時(shí), url-loader把文件轉(zhuǎn)為Data URI的格式內(nèi)聯(lián)到引用的地方
當(dāng)文件大于limit時(shí), url-loader會(huì)調(diào)用file-loader, 把文件儲(chǔ)存到輸出目錄, 并把引用的文件路徑改寫成輸出后的路徑
比如 views/foo/index.html中
![](smallpic.png)
會(huì)被編譯成
[站外圖片上傳中……(2)]
而
[站外圖片上傳中……(3)]
會(huì)被編譯成
[站外圖片上傳中……(4)]
*/
use: [
{
loader: 'url-loader',
options: {
limit: 10000
}
}
]
}
]
},
/*
配置webpack插件
plugin和loader的區(qū)別是, loader是在import時(shí)根據(jù)不同的文件名, 匹配不同的loader對(duì)這個(gè)文件做處理,
而plugin, 關(guān)注的不是文件的格式, 而是在編譯的各個(gè)階段, 會(huì)觸發(fā)不同的事件, 讓你可以干預(yù)每個(gè)編譯階段.
*/
plugins: [
/*
html-webpack-plugin用來打包入口html文件
entry配置的入口是js文件, webpack以js文件為入口, 遇到import, 用配置的loader加載引入文件
但作為瀏覽器打開的入口html, 是引用入口js的文件, 它在整個(gè)編譯過程的外面,
所以, 我們需要html-webpack-plugin來打包作為入口的html文件
*/
new HtmlWebpackPlugin({
/*
template參數(shù)指定入口html文件路徑, 插件會(huì)把這個(gè)文件交給webpack去編譯,
webpack按照正常流程, 找到loaders中test條件匹配的loader來編譯, 那么這里html-loader就是匹配的loader
html-loader編譯后產(chǎn)生的字符串, 會(huì)由html-webpack-plugin儲(chǔ)存為html文件到輸出目錄, 默認(rèn)文件名為index.html
可以通過filename參數(shù)指定輸出的文件名
html-webpack-plugin也可以不指定template參數(shù), 它會(huì)使用默認(rèn)的html模板.
*/
template: './src/index.html'
})
],
/*
配置開發(fā)時(shí)用的服務(wù)器, 讓你可以用 http://127.0.0.1:8080/ 這樣的url打開頁面來調(diào)試
并且?guī)в袩岣碌墓δ? 打代碼時(shí)保存一下文件, 瀏覽器會(huì)自動(dòng)刷新. 比nginx方便很多
如果是修改css, 甚至不需要刷新頁面, 直接生效. 這讓像彈框這種需要點(diǎn)擊交互后才會(huì)出來的東西調(diào)試起來方便很多.
*/
devServer: {
// 配置監(jiān)聽端口, 因?yàn)?080很常用, 為了避免和其他程序沖突, 我們配個(gè)其他的端口號(hào)
port: 8100,
/*
historyApiFallback用來配置頁面的重定向
SPA的入口是一個(gè)統(tǒng)一的html文件, 比如
http://localhost:8010/foo
我們要返回給它
http://localhost:8010/index.html
這個(gè)文件
配置為true, 當(dāng)訪問的文件不存在時(shí), 返回根目錄下的index.html文件
*/
historyApiFallback: true
}
}
8.走一個(gè)
./node_modules/.bin/webpack-dev-server -d --hot
npm會(huì)把包的可執(zhí)行文件安裝到./node_modules/.bin/目錄下, 所以我們要在這個(gè)目錄下執(zhí)行命令.
-d參數(shù)是開發(fā)環(huán)境(Development)的意思, 它會(huì)在我們的配置文件中插入調(diào)試相關(guān)的選項(xiàng), 比如打開debug, 打開sourceMap, 代碼中插入源文件路徑注釋.
--hot開啟熱更新功能, 參數(shù)會(huì)幫我們往配置里添加HotModuleReplacementPlugin插件, 雖然可以在配置里自己寫, 但有點(diǎn)麻煩, 用命令行參數(shù)方便很多.
命令執(zhí)行后, 控制臺(tái)的最后一行應(yīng)該是
webpack: bundle is now VALID.
這就代表編譯成功了, 我們可以在瀏覽器打開http://localhost:8100/foo 看看效果.
要退出編譯, 按ctrl+c.
開發(fā)環(huán)境編譯試過之后, 我們?cè)囋嚳淳幾g生產(chǎn)環(huán)境的代碼, 命令是:
./node_modules/.bin/webpack -p
-p參數(shù)會(huì)開啟生產(chǎn)環(huán)境模式, 這個(gè)模式下webpack會(huì)將代碼做壓縮等優(yōu)化.
大家可能會(huì)發(fā)現(xiàn), 執(zhí)行腳本的命令有點(diǎn)麻煩. 因此, 我們可以利用npm的特性, 把命令寫在package.json 中:
{
"scripts": {
"dev": "webpack-dev-server -d --hot --env.dev",
"build": "webpack -p"
}
}
package.json中的scripts對(duì)象, 可以用來寫一些腳本命令, 命令不需要前綴目錄 ./node_modules/.bin/, npm會(huì)自動(dòng)尋找該目錄下的命令. 我們可以執(zhí)行
npm run dev
來啟動(dòng)開發(fā)環(huán)境.
執(zhí)行
npm run build
來打包生產(chǎn)環(huán)境的代碼.
進(jìn)階配置
上面的項(xiàng)目雖然可以跑起來了, 但有幾個(gè)點(diǎn)我們還沒有考慮到:
指定靜態(tài)資源的url路徑前綴
各個(gè)頁面分開打包
打包時(shí)區(qū)分開發(fā)環(huán)境和生產(chǎn)環(huán)境
輸出的entry文件加上hash
第三方庫和業(yè)務(wù)代碼分開打包
開發(fā)環(huán)境關(guān)閉performance.hints
配置favicon
開發(fā)環(huán)境允許其他電腦訪問
打包時(shí)自定義部分參數(shù)
webpack-dev-server處理帶后綴名的文件的特殊規(guī)則
代碼中插入環(huán)境變量
簡(jiǎn)化import路徑
優(yōu)化babel編譯后的代碼性能
使用webpack 2自帶的ES6模塊處理功能
使用autoprefixer自動(dòng)創(chuàng)建css的vendor prefixes
編譯前清空dist目錄
那么, 讓我們?cè)谏厦娴呐渲玫幕A(chǔ)上繼續(xù)完善, 下面的代碼我們只寫出改變的部分. 代碼在examples/advanced目錄.
詳見【 https://zhuanlan.zhihu.com/p/27046322】