Webpack
第一章 Webpack 簡(jiǎn)介
Instagram團(tuán)隊(duì)在進(jìn)行前端開(kāi)發(fā)的過(guò)程中双霍,發(fā)現(xiàn)當(dāng)項(xiàng)目組成員越來(lái)越多的時(shí)候刻蟹,我們會(huì)用到很多的js框架和css框架烘绽,項(xiàng)目發(fā)布打包的時(shí)候會(huì)變得很麻煩呢燥,因此webpack就誕生了班缎。
現(xiàn)今的很多網(wǎng)頁(yè)其實(shí)可以看做是功能豐富的應(yīng)用各聘,它們擁有著復(fù)雜的JavaScript代碼和一大堆依賴包揣非。為了簡(jiǎn)化開(kāi)發(fā)的復(fù)雜度,前端社區(qū)涌現(xiàn)出了很多好的實(shí)踐方法
- 模塊化躲因,讓我們可以把復(fù)雜的程序細(xì)化為小的文件;
- 類似于TypeScript這種在JavaScript基礎(chǔ)上拓展的開(kāi)發(fā)語(yǔ)言:使我們能夠?qū)崿F(xiàn)目前版本的JavaScript不能直接使用的特性早敬,并且之后還能能裝換為JavaScript文件使瀏覽器可以識(shí)別;
- Scss大脉,Less等CSS預(yù)處理器
這些改進(jìn)確實(shí)大大的提高了我們的開(kāi)發(fā)效率搞监,但是利用它們開(kāi)發(fā)的文件往往需要進(jìn)行額外的處理才能讓瀏覽器識(shí)別,而手動(dòng)處理又是非常繁瑣的,這就為WebPack類的工具的出現(xiàn)提供了需求箱靴。
WebPack可以看做是模塊打包機(jī):它做的事情是腺逛,分析你的項(xiàng)目結(jié)構(gòu),找到JavaScript模塊以及其它的一些瀏覽器不能直接運(yùn)行的拓展語(yǔ)言(Scss衡怀,TypeScript等)棍矛,并將其打包為合適的格式以供瀏覽器使用。
Webpack真正熱起來(lái)是由于對(duì)React中的HMR(Hot Module Replacement)熱刷新技術(shù)進(jìn)行了支持抛杨,并且現(xiàn)在很好的作為工具為Vue等框架的項(xiàng)目開(kāi)發(fā)提供了支持够委。
Webpack以項(xiàng)目目錄下的index.js作為入口,使用加載器Loader加載不同類型的資源并打包怖现,在打包的過(guò)程中可以使用插件對(duì)打包內(nèi)容進(jìn)行修飾茁帽,最后ouput輸出一個(gè)合并后的文件。非常適合團(tuán)隊(duì)合作屈嗤,迭代潘拨,打包,代碼分割饶号。
對(duì)于開(kāi)發(fā)者而言,Gulp/Grunt是一種能夠優(yōu)化前端的開(kāi)發(fā)流程的工具铁追,而WebPack是一種模塊化的解決方案,不過(guò)Webpack的優(yōu)點(diǎn)使得Webpack可以替代Gulp/Grunt類的工具茫船。
webpack.github.io/
第二章 Webpack項(xiàng)目初始化
2.1 安裝
進(jìn)入項(xiàng)目文件夾琅束,使用npm初始化項(xiàng)目,生成package.json
npm init -y
npm install webpack@2 --save-dev
webpack會(huì)作為依賴安裝與我們的項(xiàng)目下算谈。
2.2 使用
簡(jiǎn)單嘗試使用webpack
在目錄下新建文件夾
+ 001 + node_modules
|- package.json
|- app +
|-index.js
index.js
console.log('hello world')
對(duì)該js進(jìn)行打包操作
D:\Material\MarkDown\Web\webpack\code\001>node_modules\.bin\webpack app/index.js ./bulid/index.js
Hash: 2001eaad3629d99e2fbb
Version: webpack 2.6.1
Time: 60ms
Asset Size Chunks Chunk Names
index.js 2.66 kB 0 [emitted] main
[0] ./app/index.js 25 bytes {0} [built]
發(fā)現(xiàn) 文件index.js被打包成為了js的模塊模式
2.3 配置Webpack
運(yùn)行node_modules下的webpack
D:\Material\MarkDown\Web\webpack\code\001>node_modules\.bin\webpack
No configuration file found and no output filename configured via CLI option.
A configuration file could be named 'webpack.config.js' in the current directory.
Use --help to display the CLI options.
我們的項(xiàng)目需要依賴于webpack.config.js文件,該文件即為webpack的配置文件.
接下來(lái)嘗試針對(duì)模塊化的js代碼進(jìn)行打包(代碼分割)并對(duì)webpack.config.js文件進(jìn)行配置.
在app目錄下新增文件并修改index.js
index.js
let component = require("./component.js")
document.body.appendChild(component());
component.js
module.exports = (text = 'hello world')=>{
const element = document.createElement('div')
element.innerHTML = text;
return element
}
使用html-webpack-plugin插件快速創(chuàng)建html文件
npm install html-webpack-plugin --save-dev
我們先把配置復(fù)制到根目錄下的webpack.config.js中,至于每一段話的內(nèi)容,我們將會(huì)在后邊逐一講解.
webpack.config.js
const path = require('path'); //引入path模塊
const HtmlWebpackPlugin = require('html-webpack-plugin') //引入html生成模塊
const PATHS = {
app : path.join(__dirname , 'app'), //定義app目錄位置為當(dāng)前目錄下的app文件夾
build : path.join(__dirname , 'build') //定義entry目錄位置為當(dāng)前目錄下的entry文件夾
};
module.exports = {
entry : {
app : PATHS.app, //入口目錄
//entry是頁(yè)面入口文件配置涩禀,可以是一個(gè)文件或者多個(gè)入口文件,可以是對(duì)象格式或者數(shù)組格式然眼。
},
output : {
path : PATHS.build, //輸出目錄
filename : "[name].js", //name對(duì)應(yīng)entry的鍵值,也可以使用[id],[hash]避免重復(fù)
//output 是對(duì)應(yīng)輸出項(xiàng)配置,主要包括path,filename和publishPath屬性艾船。path代表輸出的路徑,filename代表輸出的文件名稱,publishPath代表靜態(tài)資源發(fā)布后的前綴地址丽声。
},
plugins : [ //插件名稱和配置
new HtmlWebpackPlugin({
title : 'webpack demo',
}),
],
};
此時(shí)在根目錄運(yùn)行node_modules下的webpack,發(fā)現(xiàn)編譯成功,我們的具有引用關(guān)系的js被合并成為了app.js,并且通過(guò)插件生成了html.
因此,我們的代碼規(guī)則是通過(guò)該config文件所配置的.
注意,此時(shí)我們更改原有代碼并不能改變輸出結(jié)果,仍然需要編譯.
2.4 webpack編譯輸出日志
Hash: 0bf4dbdb0b0709763ab0 //編譯產(chǎn)生的唯一值
Version: webpack 2.6.1 //webpack版本號(hào)
Time: 968ms //編譯時(shí)長(zhǎng)
Asset Size Chunks Chunk Names //生成文件 大小 塊號(hào) 塊名
app.js 2.93 kB 0 [emitted] app
index.html 180 bytes [emitted]
[0] ./app/component.js 138 bytes {0} [built] //編譯用到的文件
[1] ./app/index.js 82 bytes {0} [built]
Child html-webpack-plugin for "index.html": //編譯用到的插件
[0] ./~/lodash/lodash.js 540 kB {0} [built] //插件引起的變化
[1] ./~/html-webpack-plugin/lib/loader.js!./~/html-web
[2] (webpack)/buildin/global.js 509 bytes {0} [built]
[3] (webpack)/buildin/module.js 517 bytes {0} [built]
如果日志如上述輸出,則編譯正常
第三章 構(gòu)建自動(dòng)刷新的代碼
3.1 使用快捷方式進(jìn)行編譯
因?yàn)槲覀儾](méi)有全局安裝全局安裝,所以項(xiàng)目?jī)?nèi)的webpack只能使用bin目錄下的文件.
我們可以配置package.json讓我們命令行直接使用:
在package.json中的scripts下新添一行
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"build":"webpack"
},
在命令行運(yùn)行npm run
可以看到我們可執(zhí)行的腳本命令
直接運(yùn)行 npm build
可以根據(jù)webpack.config.js
文件編譯當(dāng)前目錄
3.2 配置WDS進(jìn)行瀏覽器自動(dòng)刷新
項(xiàng)目目錄下安裝webpack-dev-server
npm install webpack-dev-server --save-dev
安裝完畢 修改package.json
"scripts":{
"start":"webpack-dev-server --env development",//設(shè)置為開(kāi)發(fā)環(huán)境
"build":"webpack --env production" //設(shè)置為打包環(huán)境
}
//其實(shí)直接敲命令即可,我們只是使代碼更簡(jiǎn)潔
webpack-dev-server
打包的bundle只是存在于內(nèi)存當(dāng)中,我們想要輸出還需運(yùn)行build
命令.
運(yùn)行 npm run start
我們的WDS會(huì)啟動(dòng)一個(gè)服務(wù)器,瀏覽器訪問(wèn)'127.0.0.1:8080',會(huì)顯示我們的代碼,并且修改后的頁(yè)面會(huì)即時(shí)刷新,代碼能夠動(dòng)態(tài)呈現(xiàn).
配置WDS端口號(hào)
默認(rèn)情況下WDS讀取的端口號(hào)為全局變量中的數(shù)據(jù)
在webpack.config.js
中默認(rèn)參數(shù)為:
devServer:{
host:porcess.env.HOST, //localhost
port:porcess.env.PORT //8080
}
我們只需要修改響應(yīng)的參數(shù)即可修改WDS的端口和主機(jī).
第四章 配置ESLint實(shí)現(xiàn)代碼規(guī)范自動(dòng)測(cè)試
ESLint是檢測(cè)js代碼規(guī)范的插件.
ESLint規(guī)則 http://eslint.cn/docs/rules/
新版本的ESLint甚至可以幫助我們進(jìn)行代碼修復(fù)
4.1 在普通項(xiàng)目中使用
新建項(xiàng)目目錄,初始化后安裝依賴
npm install eslint --save-dev
ESLint可配置的方式有很多
- javascript: 使用javascript導(dǎo)出一個(gè)包含配置的對(duì)象
- YAML: .eslintrc.yaml或者 .eslintrc.yml
- JSON: .eslintrc.json
- package.json : 創(chuàng)建eslintConfig屬性,所有的配置包含在屬性當(dāng)中
我們?cè)?code>package.json中配置腳本
scripts:{
"eslint": "eslint"
}
直接在項(xiàng)目中運(yùn)行,檢測(cè)代碼:
npm run eslint .\app\component.js --cache
報(bào)錯(cuò)并提示運(yùn)行eslint --init
實(shí)現(xiàn)初始化并配置文件,我們按照上述配置方式新建.eslintrc.js
文件
module.exports = {
env: { //Environment可以預(yù)設(shè)好的其他環(huán)境的全局變量礁蔗,如brower觉义、node環(huán)境變量雁社、es6環(huán)境變量、mocha環(huán)境變量等
browser: true,
commonjs: true,
es6: true,
node: true,
},
extends: 'eslint:recommended',
parserOptions: {
//ecmaVersion:6, //sLint通過(guò)parserOptions晒骇,允許指定校驗(yàn)的ecma的版本霉撵,及ecma的一些特性
sourceType: 'module',//指定來(lái)源的類型,有兩種”script”或”module”
},
rules: {
'comma-dangle': ['error', 'always-multiline'],
'linebreak-style': ['error', 'windows'],
'quotes': ['error', 'single'],
'semi': ['error', 'always'],
'no-unused-vars': ['warn'],
'no-console': 0,
},
};
ESLint可以被配置的信息主要分為3類:
- Environments:你的 javascript 腳步將要運(yùn)行在什么環(huán)境(如:nodejs洪囤,browser徒坡,commonjs等)中。
- Globals:執(zhí)行代碼時(shí)腳步需要訪問(wèn)的額外全局變量瘤缩。
- Rules:開(kāi)啟某些規(guī)則喇完,也可以設(shè)置規(guī)則的等級(jí)。
規(guī)則的錯(cuò)誤等級(jí)有三種:
- 0或'off':關(guān)閉規(guī)則剥啤。
- 1或'warn':打開(kāi)規(guī)則锦溪,并且作為一個(gè)警告(并不會(huì)導(dǎo)致檢查不通過(guò))。
- 2或'error':打開(kāi)規(guī)則府怯,并且作為一個(gè)錯(cuò)誤 (退出碼為1刻诊,檢查不通過(guò))。
我們可以使用 "extends":"eslint:recommended"
來(lái)啟用推薦的規(guī)則牺丙,報(bào)告一些常見(jiàn)的問(wèn)題.
繼續(xù)運(yùn)行,我們的插件正確讀取配置文件并且能夠輸出錯(cuò)誤则涯。
如果我們想讓錯(cuò)誤自動(dòng)修正,可在命令的最后一段添加--fix
npm run eslint .\app\component.js --cache -- --fix
4.2 在webpack中使用ESLint
安裝eslint-loader
插件到依賴,完成在webpack中的ESlint配置之后即可使用.
moudle對(duì)應(yīng)loader(加載器)的配置冲簿,主要對(duì)指定類型的文件進(jìn)行操作.
在webpack中,常用的加載器是eslint-loader
和babel-loader
,而plugins用于擴(kuò)展webpack的功能粟判,相比著loader更加靈活,不用指定文件類型峦剔。常用的plugins有三個(gè)档礁,html-webpack-plugin
、commonChunkPlugin
和ExtractTextPlugin
羊异。
修改webpack.config.js
,添加如下字段
module: {
rules: [ // rules為數(shù)組事秀,保存每個(gè)加載器的配置
{
test: /\.js$/, // test屬性必須配置,值為正則表達(dá)式野舶,用于匹配文件
loader: 'eslint-loader', // loader屬性必須配置易迹,值為字符串,loader之間用平道!隔開(kāi)
exclude: /node_module/, // 對(duì)于匹配的文件進(jìn)行過(guò)濾睹欲,排除node_module目錄下的文件
//include: './src' // 指定匹配文件的范圍
enforce:'pre',//加載器的執(zhí)行順序,不設(shè)置為正常執(zhí)行【酱可選值 'pre|post' 前|后
options:{ //配置loader的options
emitWarning:true,
}
}
]
}
我們直接運(yùn)行npm run start
袋哼,則會(huì)在運(yùn)行腳本之前執(zhí)行加載器≌⑸溃可以使用eslint運(yùn)行修復(fù)之后再次執(zhí)行.
第五章 配置CSS相關(guān)設(shè)置
5.1 配置基本loader
我們對(duì)于css的處理首先要加載兩個(gè)loader
- css-loader 處理url()和@import引入的css,如果是外部鏈接,則不作處理.
- style-loader 處理style標(biāo)簽中的css代碼
安裝插件:npm install css-loader style-loader --save-dev
配置webpack.config.js
module:{
rules:[
{
test:/\.css$/,
use:['style-loader','css-loader'] //默認(rèn)的處理順序?yàn)閺挠业阶? }
]
}
在app目錄下新增文件common.css
body{
background-color:red;
}
將common.css
引到index.js中
improt './common.css'
繼續(xù)執(zhí)行npm run start
5.2 css作用域
默認(rèn)情況下涛贯,css作用域是全局的,在團(tuán)隊(duì)開(kāi)發(fā)的過(guò)程當(dāng)中,可能會(huì)出現(xiàn)重名現(xiàn)象.
我們新建兩個(gè)文件
style1
body{
background-color:red;
}
.class1{
color:blue;
}
style2
body{
background-color:white;
}
.class1{
color:red;
}
正常情況下,如果我們引入這兩個(gè)文件,會(huì)發(fā)現(xiàn)類重名現(xiàn)象,但是webpack的模塊化能夠幫助我們完成載入
修改component.js
module.exports = (text = '你好 WDS',class1,class2)=>{
const element = document.createElement('div');
element.innerHTML = text;
element.className = class1;
const p = document.createElement('p');
p.innerText = 'p line';
p.className = class2;
element.appendChild(p);
return element;
};
修改index.js
let component = require('./component.js');
import style1 from './style1.css';
import style2 from './style2.css';
document.body.appendChild(component('hello',style.class1,style2.class1));
修改webpack.config.js中css-loader的配置
{
test:/\.css$/,
use:['style-loader',{
loader:'css-loader',
options:{
modules:true,//按照模塊渲染
}
}] //默認(rèn)的處理順序?yàn)閺挠业阶?}
我們運(yùn)行npm run start
查看結(jié)果
發(fā)現(xiàn)我們的全局的body雖然顯示white
,但是我們的字體文件的顏色卻是以文件為準(zhǔn)的.因?yàn)轭惷麜?huì)被賦予一個(gè)類似hash的值.
5.3 css代碼分離
因?yàn)槲覀兊腸ss最終會(huì)以js的形式打包,而js代碼在頁(yè)面中是最后加載的,所以我們需要將css代碼分離.
安裝插件npm install extract-text-webpack-plugin@2.1.2 --save-dev
https://github.com/webpack-contrib/extract-text-webpack-plugin
在webpack.config.js中增加配置
const ExtractTextPlugin = require('extract-text-webpack-plugin');
plugins: [
new ExtractTextPlugin({
filename:'[name].css',
ignoreOrder:true, //忽略檢測(cè)
}),
],
修改rules
{
test:/\.css$/,
use: ExtractTextPlugin.extract({
fallback: "style-loader",
use: {
loader:'css-loader',
options:{
modules:"true",
}
}
})
}
use:指需要什么樣的loader去編譯文件,這里由于源文件是.css所以選擇css-loader
fallback:編譯后用什么loader來(lái)提取css文件
第六章 深入理解loader
loader的加載順序是從右到左,從下向上進(jìn)行的,并不是按照我們代碼的讀取順序?qū)嵤?
比如我們use:['style-loader','css-loader']
,實(shí)際上是
style-loader(css-loader(input))
先進(jìn)行css代碼的解析,然后對(duì)style標(biāo)簽中的css進(jìn)行解析.
Enforcing Order : 我們可以使用enforce來(lái)強(qiáng)制條件的加載順序
傳遞參數(shù)options : 我們?cè)谑褂脜?shù)傳遞的時(shí)候可以采取options對(duì)象傳遞,同時(shí)也可以寫(xiě)成鍵值對(duì)的形式
use: {
loader:'css-loader',
options:{
modules:"true",
}
}
可以寫(xiě)作
use:'css-loader?modules=true'
第七章 文件壓縮
webpack在打包之后并未進(jìn)行代碼壓縮,我們可以通過(guò)配置實(shí)現(xiàn)文件壓縮
首先我們已經(jīng)配置好了npm run build
作為打包環(huán)境蔚出,打包后的大小會(huì)比運(yùn)行環(huán)境下的要小弟翘。
同樣,我們可以配置插件來(lái)完成代碼壓縮
npm install babili-webpack-plugin --save-dev
const BabiliPlugin = require('babili-webpack-plugin');
plugins: [
new BabiliPlugin(),
],
再次編譯骄酗,發(fā)現(xiàn)代碼會(huì)變得更小
第八章 SourceMap
我們的代碼壓縮過(guò)后反而不利于調(diào)試稀余,可以借助配置來(lái)獲取SourceMap
JavaScript腳本正變得越來(lái)越復(fù)雜。大部分源碼(尤其是各種函數(shù)庫(kù)和框架)都要經(jīng)過(guò)轉(zhuǎn)換趋翻,才能投入生產(chǎn)環(huán)境睛琳。
常見(jiàn)的源碼轉(zhuǎn)換,主要是以下三種情況:
+ (1)壓縮踏烙,減小體積师骗。比如jQuery 1.9的源碼,壓縮前是252KB宙帝,壓縮后是32KB丧凤。
+ (2)多個(gè)文件合并,減少HTTP請(qǐng)求數(shù)步脓。
+ (3)其他語(yǔ)言編譯成JavaScript愿待。最常見(jiàn)的例子就是CoffeeScript。
這三種情況靴患,都使得實(shí)際運(yùn)行的代碼不同于開(kāi)發(fā)代碼仍侥,除錯(cuò)(debug)變得困難重重。
通常鸳君,JavaScript的解釋器會(huì)告訴你农渊,第幾行第幾列代碼出錯(cuò)。但是或颊,這對(duì)于轉(zhuǎn)換后的代碼毫無(wú)用處砸紊。舉例來(lái)說(shuō),jQuery 1.9壓縮后只有3行囱挑,每行3萬(wàn)個(gè)字符醉顽,所有內(nèi)部變量都改了名字。你看著報(bào)錯(cuò)信息平挑,感到毫無(wú)頭緒游添,根本不知道它所對(duì)應(yīng)的原始位置系草。
這就是Source map想要解決的問(wèn)題。
簡(jiǎn)單說(shuō)唆涝,Sourcemap就是一個(gè)信息文件找都,里面儲(chǔ)存著位置信息。也就是說(shuō)廊酣,轉(zhuǎn)換后的代碼的每一個(gè)位置能耻,所對(duì)應(yīng)的轉(zhuǎn)換前的位置。
有了它啰扛,出錯(cuò)的時(shí)候嚎京,除錯(cuò)工具將直接顯示原始代碼,而不是轉(zhuǎn)換后的代碼隐解。這無(wú)疑給開(kāi)發(fā)者帶來(lái)了很大方便。
我們?cè)趙ebpack.config.js中進(jìn)行配置
devtool:"source-map"