?最近為了能夠?qū)懸环葜档脜⒖嫉膚ebpack文檔拒课,特意的去查了好多相應(yīng)的書籍畦韭,博客间校。距離上次寫的那篇文章好想也過去將近一周的時(shí)間了碌宴。我想是時(shí)候要準(zhǔn)備下一篇文章了忌怎。不然就食言而肥了。
?算了酪夷,技術(shù)類文章就直接從技術(shù)類開始說起吧榴啸。首先,學(xué)習(xí)webpack呢晚岭?是因?yàn)槲以陂_發(fā)vue和react的時(shí)候遇到了這個(gè)工具鸥印,然后最近在看人家的招聘要求的時(shí)候,總是會(huì)帶上這個(gè)坦报。
然后我就趁著下班時(shí)間研究了一下這個(gè)東西库说。還是和之前的說法一樣,如果有任何的疑惑燎竖,請(qǐng)?jiān)诹粞詤^(qū)留言璃弄,如果我能看見一定會(huì)及時(shí)的向您反饋要销。
新弄的github代碼地址
1构回、新語言的誕生背景
?近些年來web應(yīng)用的功能需求的完善和所設(shè)計(jì)到領(lǐng)域越發(fā)寬廣導(dǎo)致前端萌生了很多新的思想和框架。我簡(jiǎn)單的介紹一下:
?首先行業(yè)的領(lǐng)導(dǎo)著提出模塊化的思想疏咐,他們認(rèn)為將一個(gè)復(fù)雜的系統(tǒng)分為多個(gè)模塊來開發(fā)會(huì)大大減少開發(fā)難度和提升開發(fā)效率纤掸。同時(shí)考慮到css只能用靜態(tài)的語法描述元素的樣式,無法像寫javascript那樣增加邏輯判斷與共享變量浑塞。于是在這種大環(huán)境下借跪,誕生了es6、typescript酌壕、scss等新的語言掏愁。但是考慮到例如es6無法在瀏覽器中直接運(yùn)行,需要將es6轉(zhuǎn)換成es5之后瀏覽器才能識(shí)別卵牍。于是新的構(gòu)建工具就出現(xiàn)了果港。
?構(gòu)建工具的功能主要是:進(jìn)行代碼轉(zhuǎn)換、文件壓縮糊昙、代碼分割辛掠、模塊合并、自動(dòng)刷新释牺、代碼檢驗(yàn)萝衩、自動(dòng)發(fā)布等功能。構(gòu)建其實(shí)是工程化没咙、自動(dòng)化思想在前端開發(fā)的體驗(yàn)猩谊,我們要做的其實(shí)是用代碼去讓前端項(xiàng)目自動(dòng)化的執(zhí)行這一系列化復(fù)雜的流程罷了。
?說到構(gòu)建工具祭刚,我看百度上有很多牌捷。例如npm script队塘、grunt、gulp宜鸯、fis3憔古、webpack等等。因?yàn)楸疚闹饕侵vwebpack淋袖,如果有機(jī)會(huì)能接觸到以上的幾個(gè)工具鸿市,我會(huì)另外詳細(xì)的描述。
2即碗、 webpack的優(yōu)勢(shì)
?從我這些天無論是自己的實(shí)踐還是書上寫的來說焰情,我認(rèn)為webpack本身就是特別符合模塊化開發(fā)的一種工具。在webpack里面一切文件都是模塊剥懒,并通過loader轉(zhuǎn)換文件内舟,通過plugin注入鉤子,最后輸出由多模塊組合成的文件初橘。其優(yōu)點(diǎn)主要是:
1验游、 專注于處理模塊化開發(fā)的項(xiàng)目,能做到開箱即用保檐,一步到位
2耕蝉、 能通過plugin擴(kuò)展
3、 應(yīng)用各種領(lǐng)域
4夜只、 社區(qū)龐大
5垒在、 良好的開發(fā)體驗(yàn)
但談及為什么要選用webpack,我看書上主要有以下幾個(gè)看法:
1扔亥、大多數(shù)團(tuán)隊(duì)在開發(fā)新項(xiàng)目的時(shí)候都會(huì)采用緊跟時(shí)代的技術(shù)场躯,這些技術(shù)基本都會(huì)采用“模塊化+新語言+新框架”,webpack可以為這些新項(xiàng)目提供一站式的解決方案
2旅挤、webpack有良好的生態(tài)鏈和維護(hù)團(tuán)隊(duì)踢关,能提供一定的開發(fā)體驗(yàn)并保證質(zhì)量
3、webpack被全世界大量的web開發(fā)者使用和驗(yàn)證谦铃,能找到各個(gè)層次面所需的教程和經(jīng)驗(yàn)分享
(反正耘成,綜上所述 嗯 你再不學(xué)webpack就out了??)
3、 webpack的安裝(需要node環(huán)境滴)
-
初始化項(xiàng)目
如上圖所示驹闰,新建了一個(gè)項(xiàng)目瘪菌。
npm init
-
全局安裝
// 最新版本好像變成了webpack-cli注意一下 npm install -g webpack
-
項(xiàng)目?jī)?nèi)安裝
npm install webpack --save-dev
?在這里扯一句閑話,可能很多教程談到安裝webpack都會(huì)讓你選擇直接-g嘹朗,但是我并不推薦你這么做,我總感覺-g之后就成了全局變量师妙,但是我并不是每個(gè)項(xiàng)目?jī)?nèi)都能用到這個(gè)所謂的webpack,將webpack的作用域設(shè)置為項(xiàng)目?jī)?nèi)屹培,功能與全局沒有差別默穴。
4怔檩、webpack的基礎(chǔ)使用
如下圖所示,在上面新建的項(xiàng)目下面新建index.js.蓄诽。然后敲一些簡(jiǎn)單的js代碼:
// 打包代碼
webpack-cli index.js --output build/bundle.js --mode development
打包成功之后就會(huì)發(fā)現(xiàn)目錄下面多了一個(gè)build目錄薛训,里面有bundle.js文件,最后新建index,html仑氛,并引用該js,就會(huì)看到如下的效果 cnpm install webpack-dev-server --save
// 兩步
webpack-cli
webpack-dev-server
但是雖然是看到了本地編譯的html被弄上了去,但是我們發(fā)現(xiàn)每次都需要編譯文件之后再進(jìn)行webpack-dev-server插件弄到網(wǎng)上去孽水,但是我每當(dāng)js改變之后 并沒能夠自動(dòng)編譯票腰,挺麻煩的。于是我便做了如下操作
但是此時(shí)的問題又來了女气,對(duì)于一個(gè)初學(xué)者杏慰,誰會(huì)記得這么長(zhǎng),這么麻煩的代碼呢炼鞠?在Android里面一般這種東西都會(huì)用到一個(gè)配置文件寫好缘滥,然后每次都直接使用就完事兒了。說道配置文件谒主,這里就想到了webpack.json朝扼。于是就在script標(biāo)簽下面添加如下代碼,也能達(dá)到剛剛的效果
cnpm install html-webpack-plugin --save
然后加入配置項(xiàng)
以上就是我無論是看其他書籍允跑,還是博客外加上閱讀官網(wǎng)上得來的一些經(jīng)驗(yàn)意述。雖然做的東西比較簡(jiǎn)單,但是這都無疑體現(xiàn)出了webpack的優(yōu)良的品質(zhì)吮蛹。如果您對(duì)以上內(nèi)容存在有任何的疑慮或者是有任何指教荤崇,歡迎提出 我會(huì)第一時(shí)間與您交流、討論潮针。
好了术荤,webpack的入門相信大家看到這里都已經(jīng)入門了,接下來我就結(jié)合一下具體的vue實(shí)際的例子來和大家進(jìn)行討論每篷。
5瓣戚、vue中的webpack
我在這里利用的就是我用vue-cli腳手架新生成的一個(gè)vue項(xiàng)目,就結(jié)合我這段時(shí)間的一些所見所聞來做一個(gè)簡(jiǎn)單的贅述:
- index.js
'use strict' // 嚴(yán)格模式
// Template version: 1.2.7
// see http://vuejs-templates.github.io/webpack for documentation.
const config = require('./config') // 導(dǎo)入config文件
const path = require('path') //使用Node自帶的文件路徑插件
module.exports = {
// 開發(fā)環(huán)境
dev: {
// Paths
assetsSubDirectory: 'static', // 編譯輸出的二級(jí)目錄
assetsPublicPath: '/', // 編譯發(fā)布上線路徑的根目錄焦读,可配置為資源服務(wù)器域名或 CDN 域名
proxyTable: {
}, // 需要 proxyTable 代理的接口(可跨域)子库,詳情請(qǐng)看之前的文章
// Various Dev Server settings
host: '0.0.0.0', // host,如果設(shè)置成0.0.0.0可以通過統(tǒng)一局域網(wǎng)其他設(shè)備通過ip訪問該網(wǎng)頁
port: 8080, // 網(wǎng)頁默認(rèn)端口號(hào)矗晃,如果端口被占用會(huì)自動(dòng)分配一個(gè)隨即未被占有的端口
autoOpenBrowser: false, // 是否自動(dòng)打開瀏覽器
errorOverlay: true, // 在瀏覽器是否展示錯(cuò)誤蒙層
notifyOnErrors: true, // 是否展示錯(cuò)誤的通知
// 這個(gè)是webpack-dev-servr的watchOptions的一個(gè)選項(xiàng)仑嗅,指定webpack檢查文件的方式
// 因?yàn)閣ebpack使用文件系統(tǒng)去獲取文件改變的通知。在有些情況下张症,這個(gè)可能不起作用仓技。例如,當(dāng)使用NFC的時(shí)候俗他,
// vagrant也會(huì)在這方面存在很多問題脖捻,在這些情況下,使用poll選項(xiàng)(以輪詢的方式去檢查文件是否改變)可以設(shè)定為true
// 或者具體的數(shù)值兆衅,指定文件查詢的具體周期地沮。
poll: false,
// Use Eslint Loader?
useEslint: true,// eslint代碼檢查
showEslintErrorsInOverlay: false, // 如果設(shè)置為true,在瀏覽器中羡亩,eslint的錯(cuò)誤和警告會(huì)以蒙層的方式展現(xiàn)摩疑。
/**
* Source Maps
*/
// https://webpack.js.org/configuration/devtool/#development
devtool: 'eval-source-map', // 調(diào)試工具
cacheBusting: true, // 指定是否通過在文件名稱后面添加一個(gè)查詢字符串來創(chuàng)建source map的緩存
cssSourceMap: false, // 是否開啟 cssSourceMap
},
// 正式環(huán)境
build: {
// Template for index.html
index: path.resolve(__dirname, '../dist/index.html'), // 編譯注入的 index.html 文件,必須是本地的絕對(duì)路徑
// Paths
assetsRoot: path.resolve(__dirname, '../dist'), // 編譯輸出的靜態(tài)資源根路徑
assetsSubDirectory: 'static', // 編譯輸出的二級(jí)目錄
assetsPublicPath: config.getOutPutPath(), // 編譯發(fā)布上線路徑的根目錄,可配置為資源服務(wù)器域名或 CDN 域名
// assetsPublicPath: './',
/**
* Source Maps
*/
productionSourceMap: true, //生成用于生產(chǎn)構(gòu)建的源映射
devtool: '#source-map', // 調(diào)試代碼的模式夕春,共有7種未荒,這里是生成source-map文件
productionGzip: false, // 是否開啟 gzip
productionGzipExtensions: ['js', 'css'], // 需要使用 gzip 壓縮的文件擴(kuò)展名
// 一個(gè)實(shí)用工具,用于分析項(xiàng)目的依賴關(guān)系
// 如果這個(gè)選項(xiàng)是true的話,那么則會(huì)在build后及志,會(huì)在瀏覽器中生成一份bundler報(bào)告
bundleAnalyzerReport: process.env.npm_config_report
}
}
- utils.js
const path = require('path') // 引入nodejs的path模塊片排,用于操作路徑
const config = require('../config') // 引入模板的配置文件寨腔,下面就需要去這個(gè)文件中看看有什么基本的配置
const ExtractTextPlugin = require('extract-text-webpack-plugin') // 提取特定文件的插件,比如把css文件提取到一個(gè)文件中去
const packageConfig = require('../package.json') // 加載package.json文件
// 生成編譯輸出的二級(jí)目錄
exports.assetsPath = function (_path) {
const assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
// path.posix是path模塊跨平臺(tái)的實(shí)現(xiàn)(不同平臺(tái)的路徑表示是不一樣的)
return path.posix.join(assetsSubDirectory, _path)
}
// 為不同的css預(yù)處理器提供一個(gè)統(tǒng)一的生成方式率寡,也就是統(tǒng)一處理各種css類型的打包問題迫卢。
// 這個(gè)是為在vue文件中的style中使用的css類型
exports.cssLoaders = function (options) {
options = options || {}
// 打包c(diǎn)ss模塊
const cssLoader = {
loader: 'css-loader',
options: {
sourceMap: options.sourceMap
}
}
// 編譯postcss模塊
const postcssLoader = {
// 使用postcss-loader來打包postcss模塊
loader: 'postcss-loader',
// 配置source map
options: {
sourceMap: options.sourceMap
}
}
// 創(chuàng)建loader加載器字符串,結(jié)合extract text插件使用
/**
*
* loader:loader的名稱
* loaderOptions:loader對(duì)應(yīng)的options配置對(duì)象
*/
function generateLoaders (loader, loaderOptions) {
// 通過usePostCSS 來標(biāo)明是否使用了postcss
const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader]
// 如果指定了具體的loader的名稱
if (loader) {
// 向loaders的數(shù)組中添加該loader對(duì)應(yīng)的加載器
// 一個(gè)很重要的地方就是冶共,一個(gè)數(shù)組中的loader加載器乾蛤,是從右向左執(zhí)行的。
loaders.push({
// loader加載器的名稱
loader: loader + '-loader',
// 對(duì)應(yīng)的加載器的配置對(duì)象
options: Object.assign({}, loaderOptions, {
sourceMap: options.sourceMap
})
})
}
// 如果明確指定了需要提取靜態(tài)文件捅僵,則使用
// ExtractTextPlugin.extract({})來包裹我們的各種css處理器家卖。
if (options.extract) {
return ExtractTextPlugin.extract({
use: loaders,
// fallback這個(gè)選項(xiàng)我們可以這樣理解
// webpack默認(rèn)會(huì)按照loaders中的加載器從右向左調(diào)用編譯各種css類型文件。如果一切順利庙楚,在loaders中的
// 各個(gè)加載器運(yùn)行結(jié)束之后就會(huì)把css文件導(dǎo)入到規(guī)定的文件中去上荡,如果不順利,則繼續(xù)使用vue-style-loader來處理
// css文件
fallback: 'vue-style-loader'
})
} else {
// 如果沒有提取行為馒闷,則最后再使用vue-style-loader處理css
return ['vue-style-loader'].concat(loaders)
}
}
// https://vue-loader.vuejs.org/en/configurations/extract-css.html
return {
css: generateLoaders(),
postcss: generateLoaders(),
less: generateLoaders('less'),
sass: generateLoaders('sass', { indentedSyntax: true }),
scss: generateLoaders('sass'),
stylus: generateLoaders('stylus'),
styl: generateLoaders('stylus')
}
}
// 使用這個(gè)函數(shù)酪捡,為那些獨(dú)立的style文件創(chuàng)建加載器配置。
exports.styleLoaders = function (options) {
// 保存加載器配置的變量
const output = []
// 獲取所有css文件類型的loaders
const loaders = exports.cssLoaders(options)
for (const extension in loaders) {
const loader = loaders[extension]
// 生成對(duì)應(yīng)的loader配置
output.push({
test: new RegExp('\\.' + extension + '$'),
use: loader
})
}
return output
}
exports.createNotifierCallback = () => {
// node-notifier是一個(gè)跨平臺(tái)的包纳账,以類似瀏覽器的通知的形式展示信息逛薇。
const notifier = require('node-notifier')
return (severity, errors) => {
// 只展示錯(cuò)誤的信息
if (severity !== 'error') return
const error = errors[0]
const filename = error.file && error.file.split('!').pop()
// 需要展示的錯(cuò)誤信息的內(nèi)容
notifier.notify({
// 通知的標(biāo)題
title: packageConfig.name,
// 通知的主體內(nèi)容
message: severity + ': ' + error.name,
// 副標(biāo)題
subtitle: filename || '',
// 通知展示的icon
icon: path.join(__dirname, 'logo.png')
})
}
}
- vue.loader.config.js
const utils = require('./utils')
const config = require('../config')
// 設(shè)置是不是生產(chǎn)環(huán)境
const isProduction = process.env.NODE_ENV === 'production'
// 根據(jù)不同的環(huán)境,引入不同的source map配置文件
const sourceMapEnabled = isProduction
? config.build.productionSourceMap
: config.dev.cssSourceMap
module.exports = {
// vue文件中的css loader配置
loaders: utils.cssLoaders({
sourceMap: sourceMapEnabled,
// 生產(chǎn)環(huán)境下就會(huì)把css文件抽取到一個(gè)獨(dú)立的文件中
extract: isProduction
}),
// css source map文件的配置
cssSourceMap: sourceMapEnabled,
// css source map文件緩存控制變量
cacheBusting: config.dev.cacheBusting,
transformToRequire: {
video: ['src', 'poster'],
source: 'src',
img: 'src',
image: 'xlink:href'
}
}
- build/webpack.base.conf.js
const path = require('path') // 使用 NodeJS 自帶的文件路徑插件
const utils = require('./utils') //封裝了一些方法的工具
const config = require('../config') //使用 config/index.js
const vueLoaderConfig = require('./vue-loader.conf') //使用vue-loader.conf
function resolve (dir) {
return path.join(__dirname, '..', dir) // 拼接我們的工作區(qū)路徑為一個(gè)絕對(duì)路徑
}
// eslint的規(guī)則
const createLintingRule = () => ({
// 對(duì).js和.vue結(jié)尾的文件進(jìn)行eslint檢查
test: /\.(js|vue)$/,
// 使用eslint-loader
loader: 'eslint-loader',
// enforce的值可能是pre和post疏虫。其中pre有點(diǎn)和webpack@1中的preLoader配置含義相似永罚。
// post和v1中的postLoader配置含義相似。表示loader的調(diào)用時(shí)機(jī)
// 這里表示在調(diào)用其他loader之前需要先調(diào)用這個(gè)規(guī)則進(jìn)行代碼風(fēng)格的檢查
enforce: 'pre',
// 需要進(jìn)行eslint檢查的文件的目錄存在的地方
include: [resolve('src'), resolve('test')],
// eslint-loader配置過程中需要指定的選項(xiàng)
options: {
// 文件風(fēng)格的檢查的格式化程序议薪,這里使用的是第三方的eslint-friendly-formatter
formatter: require('eslint-friendly-formatter'),
// 是否需要eslint輸出警告信息
emitWarning: !config.dev.showEslintErrorsInOverlay
}
})
var webpack = require('webpack')
module.exports = {
// webpack在尋找尋找相對(duì)路徑的文件時(shí)候會(huì)以context作為根目錄尤蛮。
// context默認(rèn)為執(zhí)行啟動(dòng)webpack時(shí)所在的當(dāng)前工作目錄
context: path.resolve(__dirname, '../'),
// entry表示入口,webpack構(gòu)建的第一步從entry開始
// 類型可以是string斯议,object,array
entry: {
app: './src/main.js'
},
output: {
// 輸出文件存放的目錄醇锚,必須是string類型的絕對(duì)目錄
path: config.build.assetsRoot,
// 通過entry不同生成不同的文件名字哼御,詳情請(qǐng)看文章
filename: '[name].js',
// 發(fā)布到線上所有資源的url前綴,為string類型
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath,
// 導(dǎo)出庫的名稱焊唬,為string類型恋昼,不填寫的時(shí)候,默認(rèn)輸出格式是匿名的立即執(zhí)行函數(shù)
// library: "KlivitamLibrary"
// 導(dǎo)出庫的類型赶促,為枚舉類型液肌,默認(rèn)是var
// 可選值: umd umd2 commonjs2,commonjs,amd,this,var,assign,window,global,jsonp
// libraryTarget: "jsonp"
// 是否包含游泳的文件信息到生成的代碼里
// pathinfo: true
// 附加chunk的文件名稱
// chunkFilename: "[id].js"
// chunkFilename: "[chunkhash].js"
// jsonp異步加載資源時(shí)的回調(diào)函數(shù)名
// jsonpFunction: "webpackJsonP"
// 生成source map文件的名稱
// sourceMapFilename: "[file].map"
//瀏覽器開發(fā)者工具里顯示的遠(yuǎn)嗎模塊名稱
// devtoolModuleFilenameTemplate: "webpack:///[resource-path]"
// 異步加載跨域的資源時(shí)使用的方式
// crossOriginLoading: "use-credentials",
// crossOriginLoading: "anonymous",
// crossOriginLoading: false,
},
// 配置模塊解析時(shí)候的一些選項(xiàng)
resolve: {
// 自動(dòng)補(bǔ)全的擴(kuò)展名,能夠使用戶在引入模塊時(shí)不帶擴(kuò)展
extensions: ['.js', '.vue', '.json'],
// 默認(rèn)路徑代理,例如 import Vue from 'vue$'鸥滨,會(huì)自動(dòng)到 'vue/dist/vue.esm.js'中尋找
alias: {
'vue$': 'vue/dist/vue.esm.js',
'@': resolve('src'),
'components': resolve('src/components'),
// 可以在引入文件的時(shí)候使用pages符號(hào)引入src/pages文件夾中的文件
'pages': resolve('src/pages'),
'http': resolve('src/http'),
'public': resolve('src/public'),
'jquery': 'jquery'
}
},
// 下面是針對(duì)具體的模塊進(jìn)行的具體的配置
// 下面的配置語法采用的是version >= @2的版本
module: {
noParse: [/videojs-contrib-hls/], // 不用解析和處理的模塊
// rules是一個(gè)數(shù)組嗦哆,其中的每一個(gè)元素都是一個(gè)對(duì)象谤祖,這個(gè)對(duì)象是針對(duì)具體類型的文件進(jìn)行的配置。
rules: [
...(config.dev.useEslint ? [createLintingRule()] : []),
{
test: /\.vue$/, // 正則匹配loader名字
loader: 'vue-loader', // loader名字
// 針對(duì)此加載器的具體配置
// 針對(duì)前面的分析老速,這個(gè)配置對(duì)象中包含了各種css類型文件的配置粥喜,css source map的配置 以及一些transform的配置
options: vueLoaderConfig
},
{
test: /\.js$/,
// js文件的處理主要使用的是babel-loader。在這里沒有指定具體的編譯規(guī)則橘券,babel-loader會(huì)自動(dòng)
// 讀取根目錄下面的.babelrc中的babel配置用于編譯js文件
loader: 'babel-loader',
// 指定需要進(jìn)行編譯的文件的路徑
// 這里表示只對(duì)src和test文件夾中的文件進(jìn)行編譯
include: [resolve('src'), resolve('test'), resolve('config/myapi')] //規(guī)則所包含的文件夾
},
{
// 對(duì)圖片資源進(jìn)行編譯的配置
// 指定文件的類型
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
// 使用url-loader進(jìn)行文件資源的編譯
loader: 'url-loader',
// url-loader的配置選項(xiàng)
options: {
// 文件的大小小于10000字節(jié)(10kb)的時(shí)候會(huì)返回一個(gè)dataUrl
limit: 10000,
// 生成的文件的保存路徑和后綴名稱
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{ // 對(duì)視頻進(jìn)行打包編譯
test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('media/[name].[hash:7].[ext]')
}
},
{ // 對(duì)字體進(jìn)行打包編譯
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
},
// 這些選項(xiàng)用于配置polyfill或mock某些node.js全局變量和模塊额湘。
// 這可以使最初為nodejs編寫的代碼可以在瀏覽器端運(yùn)行
node: {
// 這個(gè)配置是一個(gè)對(duì)象,其中的每個(gè)屬性都是nodejs全局變量或模塊的名稱
setImmediate: false,
// prevent webpack from injecting mocks to Node native modules
// that does not make sense for the client
// 設(shè)置成empty則表示提供一個(gè)空對(duì)象
dgram: 'empty',
fs: 'empty',
net: 'empty',
tls: 'empty',
child_process: 'empty'
}
}
+build/webpack.dev.conf.js
'use strict'
// 首先引入的是一些工具方法旁舰,下面我們就需要去util文件種看一下有哪些對(duì)應(yīng)的工具方法
const utils = require('./utils')
// 引入webpack模塊
const webpack = require('webpack')
// 引入配置文件
// 這個(gè)配置文件中包含了一些dev和production環(huán)境的基本配置
const config = require('../config')
// 引入webpack-merge模塊锋华。這個(gè)模塊用于把多個(gè)webpack配置合并成一個(gè)配置,后面的配置會(huì)覆蓋前面的配置箭窜。
const merge = require('webpack-merge')
// 引入webpack的基本設(shè)置毯焕,這個(gè)設(shè)置文件包含了開發(fā)環(huán)境和生產(chǎn)環(huán)境的一些公共配置
const baseWebpackConfig = require('./webpack.base.conf')
// 用于生成html文件的插件
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 這個(gè)插件能夠更好的在終端看到webpack運(yùn)行時(shí)的錯(cuò)誤和警告等信息≌揽欤可以提升開發(fā)體驗(yàn)
const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 查找一個(gè)未使用的端口
const portfinder = require('portfinder')
// 獲取host環(huán)境變量芥丧,用于配置開發(fā)環(huán)境域名
const HOST = process.env.HOST
// 獲取post環(huán)境變量,用于配置開發(fā)環(huán)境時(shí)候的端口號(hào)
const PORT = process.env.PORT && Number(process.env.PORT)
// 開發(fā)環(huán)境的完整的配置文件
const devWebpackConfig = merge(baseWebpackConfig, {
module: {
// 為那些獨(dú)立的css類型文件添加loader配置(沒有寫在vue文件的style標(biāo)簽中的樣式)
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true })
},
// 開發(fā)環(huán)境使用'eval-source-map'模式的source map
// 因?yàn)樗俣瓤? devtool: config.dev.devtool,
// 下面是對(duì)webpack-dev-server選項(xiàng)的基本配置坊罢,這些配置信息续担,我們可以在/config/index.js
// 文件中進(jìn)行自定義配置。
devServer: {
// 用于配置在開發(fā)工具的控制臺(tái)中顯示的日志級(jí)別
// 注意這個(gè)不是對(duì)bundle的錯(cuò)誤和警告的配置活孩,而是對(duì)它生成之前的消息的配置
clientLogLevel: 'warning',
// 表示當(dāng)使用html5的history api的時(shí)候物遇,任意的404響應(yīng)都需要被替代為index.html
historyApiFallback: true,
// 啟用webpack的熱替換特性
hot: true,
// 一切服務(wù)都需要使用gzip壓縮
// 可以在js,css等文件的response header中發(fā)現(xiàn)有Content-Encoding:gzip響應(yīng)頭
compress: true,
// 指定使用一個(gè) host憾儒。默認(rèn)是 localhost
// 如果希望服務(wù)器外部可以訪問(通過我們電腦的ip地址和端口號(hào)訪問我們的應(yīng)用)
// 可以指定0.0.0.0,使用這個(gè)可以使得同一局域網(wǎng)內(nèi)所有設(shè)備都能訪問你的本地網(wǎng)頁
host: HOST || config.dev.host,
// 指定要監(jiān)聽請(qǐng)求的端口號(hào)
port: PORT || config.dev.port,
// 是否自動(dòng)打開瀏覽器
open: config.dev.autoOpenBrowser,
// 當(dāng)編譯出現(xiàn)錯(cuò)誤的時(shí)候询兴,是否希望在瀏覽器中展示一個(gè)全屏的蒙層來展示錯(cuò)誤信息
overlay: config.dev.errorOverlay
// 表示只顯示錯(cuò)誤信息而不顯示警告信息
// 如果兩者都希望顯示,則把這兩項(xiàng)都設(shè)置為true
? { warnings: false, errors: true }
// 設(shè)置為false則表示啥都不顯示
: false,
// 指定webpack-dev-server的根目錄起趾,這個(gè)目錄下的所有的文件都是能直接通過瀏覽器訪問的
// 推薦和output.publicPath設(shè)置為一致
publicPath: config.dev.assetsPublicPath,
// 配置代理诗舰,這樣我們就可以跨域訪問某些接口
// 我們?cè)L問的接口,如果符合這個(gè)選項(xiàng)的配置训裆,就會(huì)通過代理服務(wù)器轉(zhuǎn)發(fā)我們的請(qǐng)求
proxy: config.dev.proxyTable,
// 啟用 quiet 后眶根,除了初始啟動(dòng)信息之外的任何內(nèi)容都不會(huì)被打印到控制臺(tái)。這也意味著來自 webpack 的錯(cuò)誤或警告在控制臺(tái)不可見边琉。
quiet: true, // necessary for FriendlyErrorsPlugin
// 與監(jiān)視文件相關(guān)的控制選項(xiàng)
watchOptions: {
// 如果這個(gè)選項(xiàng)為true属百,會(huì)以輪詢的方式檢查我們的文件的變動(dòng),效率不好
poll: config.dev.poll,
}
},
plugins: [
// 創(chuàng)建一個(gè)在編譯時(shí)可以配置的全局變量
new webpack.DefinePlugin({
'process.env': require('../config/dev.env')
}),
// 啟用熱替換模塊
// 記住变姨,我們永遠(yuǎn)不要再生產(chǎn)環(huán)境中使用hmr
new webpack.HotModuleReplacementPlugin(),
// 這個(gè)插件的主要作用就是在熱加載的時(shí)候直接返回更新文件的名稱族扰,而不是文件的id
new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update.
// 使用這個(gè)插件可以在編譯出錯(cuò)的時(shí)候來跳過輸出階段,這樣可以確保輸出資源不會(huì)包含錯(cuò)誤。
new webpack.NoEmitOnErrorsPlugin(),
// 這個(gè)插件主要是生成一個(gè)html文件
new HtmlWebpackPlugin({
// 生成的html文件的名稱
filename: 'index.html',
// 使用的模板的名稱
template: 'index.html',
// 將所有的靜態(tài)文件都插入到body文件的末尾
inject: true
}),
]
})
module.exports = new Promise((resolve, reject) => {
portfinder.basePort = process.env.PORT || config.dev.port
// 這種獲取port的方式會(huì)返回一個(gè)promise
portfinder.getPort((err, port) => {
if (err) {
reject(err)
} else {
// 把獲取到的端口號(hào)設(shè)置為環(huán)境變量PORT的值
process.env.PORT = port
// 重新設(shè)置webpack-dev-server的端口的值
devWebpackConfig.devServer.port = port
// 將FriendlyErrorsPlugin添加到webpack的配置文件中
devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({
// 編譯成功時(shí)候的輸出信息
compilationSuccessInfo: {
messages: [`Your application is running here: http://${devWebpackConfig.devServer.host}:${port}`],
},
// 當(dāng)編譯出錯(cuò)的時(shí)候渔呵,根據(jù)config.dev.notifyOnErrors來確定是否需要在桌面右上角顯示錯(cuò)誤通知框
onErrors: config.dev.notifyOnErrors
? utils.createNotifierCallback()
: undefined
}))
// resolve我們的配置文件
resolve(devWebpackConfig)
}
})
})
- webpack.prod.conf.js
// 引入path模塊
const path = require('path')
// 引入工具方法
const utils = require('./utils')
// 引入webpack模塊
const webpack = require('webpack')
// 引入基本的配置
const config = require('../config')
// 引入webpack-merge模塊
const merge = require('webpack-merge')
// 引入開發(fā)環(huán)境和生產(chǎn)環(huán)境公共的配置
const baseWebpackConfig = require('./webpack.base.conf')
// 這個(gè)模塊主要用于在webpack中拷貝文件和文件夾
const CopyWebpackPlugin = require('copy-webpack-plugin')
// 這個(gè)插件主要是用于基于模版生成html文件的
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 這個(gè)插件主要是用于將入口中所有的chunk怒竿,移到獨(dú)立的分離的css文件中
const ExtractTextPlugin = require('extract-text-webpack-plugin')
// 這個(gè)插件主要是用于壓縮css模塊的
const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
// 這個(gè)插件主要是用于壓縮js文件的
// const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
// 配置多份環(huán)境
let env
if(process.argv[2] === 'test'){
env=require('../config/dev.env')
}else{
env=require('../config/prod.env')
}
// 合并公共配置和生產(chǎn)環(huán)境獨(dú)有的配置并返回一個(gè)用于生產(chǎn)環(huán)境的webpack配置文件
const webpackConfig = merge(baseWebpackConfig, {
// 用于生產(chǎn)環(huán)境的一些loader配置
module: {
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
// 在生產(chǎn)環(huán)境中使用extract選項(xiàng),這樣就會(huì)把thunk中的css代碼抽離到一份獨(dú)立的css文件中去
extract: true,
usePostCSS: true
})
},
// 配置生產(chǎn)環(huán)境中使用的source map的形式厘肮。在這里愧口,生產(chǎn)環(huán)境使用的是#source map的形式
devtool: config.build.productionSourceMap ? config.build.devtool : false,
output: {
// build所產(chǎn)生的文件的存放的文件夾地址
path: config.build.assetsRoot,
// build之后的文件的名稱
// 這里[name]和[chunkhash]都是占位符
// 其中[name]指的就是模塊的名稱
// [chunkhash]chunk內(nèi)容的hash字符串,長(zhǎng)度為20
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// [id]也是一個(gè)占位符类茂,表示的是模塊標(biāo)識(shí)符(module identifier)
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
// 壓縮javascript的插件
new webpack.optimize.UglifyJsPlugin({
// 壓縮js的時(shí)候的一些基本配置
uglifyOptions: {
// 配置壓縮的行為
compress: {
// 在刪除未使用的變量等時(shí)耍属,顯示警告信息,默認(rèn)就是false
warnings: false
}
},
// 使用 source map 將錯(cuò)誤信息的位置映射到模塊(這會(huì)減慢編譯的速度)
// 而且這里不能使用cheap-source-map
sourceMap: config.build.productionSourceMap,
// 使用多進(jìn)程并行運(yùn)行和文件緩存來提高構(gòu)建速度
parallel: true
}),
// 提取css文件到一個(gè)獨(dú)立的文件中去
new ExtractTextPlugin({
// 提取之后css文件存放的地方
// 其中[name]和[contenthash]都是占位符
// [name]就是指模塊的名稱
// [contenthash]根據(jù)提取文件的內(nèi)容生成的 hash
filename: utils.assetsPath('css/[name].[contenthash].css'),
// 從所有額外的 chunk(additional chunk) 提取css內(nèi)容
// (默認(rèn)情況下巩检,它僅從初始chunk(initial chunk) 中提群衿)
// 當(dāng)使用 CommonsChunkPlugin 并且在公共 chunk 中有提取的 chunk(來自ExtractTextPlugin.extract)時(shí)
// 這個(gè)選項(xiàng)需要設(shè)置為true
allChunks: true,
}),
// duplicated CSS from different components can be deduped.
// 使用這個(gè)插件壓縮css,主要是因?yàn)榫た蓿瑢?duì)于不同組件中相同的css可以剔除一部分
new OptimizeCSSPlugin({
// 這個(gè)選項(xiàng)的所有配置都會(huì)傳遞給cssProcessor
// cssProcessor使用這些選項(xiàng)決定壓縮的行為
cssProcessorOptions: config.build.productionSourceMap
? { safe: true, map: { inline: false } }
: { safe: true }
}),
// 創(chuàng)建一個(gè)html文件
new HtmlWebpackPlugin({
// 創(chuàng)建一個(gè)html文件
filename: config.build.index,
// 使用的模板的名稱
template: 'index.html',
// 把script和link標(biāo)簽放在body底部
inject: true,
// 配置html的壓縮行為
minify: {
// 移除注釋
removeComments: true,
// 去除空格和換行
collapseWhitespace: true,
// 盡可能移除屬性中的引號(hào)和空屬性
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// chunks 目錄
chunks: ['manifest', 'vendor', 'app'],
// 控制chunks的順序领舰,這里表示按照依賴關(guān)系進(jìn)行排序
// 也可以是一個(gè)函數(shù),自己定義排序規(guī)則
chunksSortMode: 'dependency'
}),
// 根據(jù)模塊的相對(duì)路徑生成一個(gè)四位數(shù)的hash作為模塊id
new webpack.HashedModuleIdsPlugin(),
// webpack2處理過的每一個(gè)模塊都會(huì)使用一個(gè)函數(shù)進(jìn)行包裹
// 這樣會(huì)帶來一個(gè)問題:降低瀏覽器中JS執(zhí)行效率迟螺,這主要是閉包函數(shù)降低了JS引擎解析速度冲秽。
// webpack3中,通過下面這個(gè)插件就能夠?qū)⒁恍┯新?lián)系的模塊矩父,
// 放到一個(gè)閉包函數(shù)里面去锉桑,通過減少閉包函數(shù)數(shù)量從而加快JS的執(zhí)行速度。
new webpack.optimize.ModuleConcatenationPlugin(),
// 這個(gè)插件用于提取多入口chunk的公共模塊
// 通過將公共模塊提取出來之后窍株,最終合成的文件能夠在最開始的時(shí)候加載一次
// 然后緩存起來供后續(xù)使用民轴,這會(huì)帶來速度上的提升。
new webpack.optimize.CommonsChunkPlugin({
// 這是 common chunk 的名稱
name: 'vendor',
// 把所有從mnode_modules中引入的文件提取到vendor中
minChunks (module) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// 為了將項(xiàng)目中的第三方依賴代碼抽離出來球订,官方文檔上推薦使用這個(gè)插件后裸,當(dāng)我們?cè)陧?xiàng)目里實(shí)際使用之后,
// 發(fā)現(xiàn)一旦更改了 app.js 內(nèi)的代碼冒滩,vendor.js 的 hash 也會(huì)改變微驶,那么下次上線時(shí),
// 用戶仍然需要重新下載 vendor.js 與 app.js——這樣就失去了緩存的意義了开睡。所以第二次new就是解決這個(gè)問題的
// 參考:https://github.com/DDFE/DDFE-blog/issues/10
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
minChunks: Infinity
}),
// This instance extracts shared chunks from code splitted chunks and bundles them
// in a separate chunk, similar to the vendor chunk
// see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk
new webpack.optimize.CommonsChunkPlugin({
name: 'app',
async: 'vendor-async',
children: true,
minChunks: 3
}),
// copy custom static assets
// 拷貝靜態(tài)資源到build文件夾中
new CopyWebpackPlugin([
{
// 定義要拷貝的資源的源目錄
from: path.resolve(__dirname, '../static'),
// 定義要拷貝的資源的目標(biāo)目錄
to: config.build.assetsSubDirectory,
// 忽略拷貝指定的文件祈搜,可以使用模糊匹配
ignore: ['.*']
}
])
]
})
if (config.build.productionGzip) {
// 如果開啟了生產(chǎn)環(huán)境的gzip
const CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
// 目標(biāo)資源的名稱
// [path]會(huì)被替換成原資源路徑
// [query]會(huì)被替換成原查詢字符串
asset: '[path].gz[query]',
// gzip算法
// 這個(gè)選項(xiàng)可以配置成zlib模塊中的各個(gè)算法
// 也可以是(buffer, cb) => cb(buffer)
algorithm: 'gzip',
// 處理所有匹配此正則表達(dá)式的資源
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
// 只處理比這個(gè)值大的資源
threshold: 10240,
// 只有壓縮率比這個(gè)值小的資源才會(huì)被處理
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
// 如果需要生成一分bundle報(bào)告,則需要使用下面的這個(gè)插件
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
- build/check-versions.js
// 在終端為不同字體顯示不同的風(fēng)格
const chalk = require('chalk')
// 解析npm包的version
const semver = require('semver')
// 引入package.json文件
const packageConfig = require('../package.json')
// node版本的uninx shell命令
const shell = require('shelljs')
// 執(zhí)行命令的函數(shù)
function exec (cmd) {
return require('child_process').execSync(cmd).toString().trim()
}
const versionRequirements = [
{
name: 'node',
// node的版本
// process.version就是node的版本
// semver.clean('v8.8.0') => 8.8.0
currentVersion: semver.clean(process.version),
// package.json中定義的node版本的范圍
versionRequirement: packageConfig.engines.node
}
]
// 相當(dāng)于 which npm
if (shell.which('npm')) {
// 如果npm命令存在的話
versionRequirements.push({
name: 'npm',
// 檢查npm的版本
currentVersion: exec('npm --version'),
// package.json中定義的npm版本
versionRequirement: packageConfig.engines.npm
})
}
module.exports = function () {
const warnings = []
for (let i = 0; i < versionRequirements.length; i++) {
const mod = versionRequirements[i]
// semver.satisfies()進(jìn)行版本之間的比較
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
// 如果現(xiàn)有的npm或者node的版本比定義的版本低士八,則生成一段警告
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (let i = 0; i < warnings.length; i++) {
const warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}
- build/build.js
'use strict'
require('./check-versions')() // 檢查npm和node的版本
process.env.NODE_ENV = 'production' // 設(shè)置環(huán)境變量NODE_ENV的值是production
const ora = require('ora') // 終端的spinner
const rm = require('rimraf') // node.js版本的rm -rf
const path = require('path') // 使用Node自帶的文件路徑工具
const chalk = require('chalk') // 引入顯示終端顏色模塊
// const opn = require('opn') // 一個(gè)可以強(qiáng)制打開瀏覽器并跳轉(zhuǎn)到指定url的插件
const webpack = require('webpack') // 使用webpack
const config = require('../config') // 加載config的配置
const webpackConfig = require('./webpack.prod.conf') // 引入webpack在production環(huán)境下的配置文件
const spinner = ora('building for production...')
spinner.start()
// 刪除打包目標(biāo)目錄下的文件
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
webpack(webpackConfig, (err, stats) => {
// 進(jìn)行打包
spinner.stop()
if (err) throw err
// 輸出打包的狀態(tài)
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false, // If you are using ts-loader, setting this to true will make TypeScript errors show up during build.
chunks: false,
chunkModules: false
}) + '\n\n')
// 如果打包出現(xiàn)錯(cuò)誤
if (stats.hasErrors()) {
console.log(chalk.red(' Build failed with errors.\n'))
// 退出
process.exit(1)
}
console.log(chalk.cyan(' Build complete.\n'))
console.log(chalk.yellow(
' Tip: built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
))
})
})