詳解 vue-cli 的打包配置檔案程式碼
一、前言
對於webpack基礎不好,node指令不通的童鞋。估計對自己搭建Vue、react腳手架是相當頭疼的,有種無從下手的感覺。然而,從頭看這2塊,耗時太長,而且說實話得練才行,不練練手看不明白。那大多數人就採取折中的方案,修改成熟的腳手架,改成自己想要的樣子。這個相對來說難度降低了不少,不過依然會有很多難點不加不明白。所以這裡就以Vue的腳手架為例子,將原始碼加上註釋,方便大家加深對專案配置的理解,少走彎路。
二、vue-cli 都做了什麼
先跟大家簡單說說,vue-cli 都做了什麼,大家也對他有個大體的瞭解
1、build/dev-server.js 檔案 專案node的啟動檔案,這裡面做了webpack配置和node操作,
2、build/webpack.base.conf.js webpack基本配置檔案
3、build/webpack.dev.conf.js 開發環境webpack配置
4、build/webpack.prod.conf.js 正式環境的webpack配置
5、build/build.js 執行打包的配置檔案
6、config/index.js 開發、線上環境的檔案輸出、打包等一些配置
這是腳手架裡面的一些主要配置檔案,通過配置,整個專案分為開發環境和生產環境,不需要你多做很多繁瑣的工作,用起來很方便,如果你想按照自己的喜好或者需求,可以修改主題檔案的配置,下面我們就附上上述每個檔案的程式碼和註釋。 ps:程式碼我修改過,不是完全一樣的,大家主要看看檔案功能,和一些程式碼、元件的含義。
三、具體檔案程式碼詳解
1、build/dev-server.js
// 檢查 Node 和 npm 版本 require('./check-versions')() // 獲取 config/index.js 的預設配置 var config = require('../config') // 如果 Node 的環境無法判斷當前是 dev / product 環境 // 使用 config.dev.env.NODE_ENV 作為當前的環境 if (!process.env.NODE_ENV) { process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) } // 一個可以強制開啟瀏覽器並跳轉到指定 url 的外掛 var opn = require('opn') // 使用 NodeJS 自帶的檔案路徑工具 var path = require('path') // 使用 express var express = require('express') // 使用 webpack var webpack = require('webpack') // 使用 proxyTable 熱更新 var proxyMiddleware = require('http-proxy-middleware') // 使用 dev 環境的 webpack 配置 var webpackConfig = require('./webpack.dev.conf') // 使用axios請求 var axios = require('axios') // 如果沒有指定執行埠,使用 config.dev.port 作為執行埠 var port = process.env.PORT || config.dev.port var autoOpenBrowser = !!config.dev.autoOpenBrowser // 使用 config.dev.proxyTable 的配置作為 proxyTable 的代理配置 var proxyTable = config.dev.proxyTable // 使用 express 啟動一個服務,URL改一下,這裡是做服務轉發,還可以對 var app = express() var apiRoutes = express.Router() apiRoutes.get('/getDiscList', function (req, res) { var url = 'https://c.y.qq.com/splcloud/fcgi-bin/fcg_get_diss_by_tag.fcg' axios.get(url, { headers: { referer: 'https://c.y.qq.com/', host: 'c.y.qq.com' }, params: req.query }).then((response) => { res.json(response.data) }).catch((e) => { console.log(e) }) }) apiRoutes.get('/lyric', function (req, res) { var url = 'https://c.y.qq.com/lyric/fcgi-bin/fcg_query_lyric_new.fcg' axios.get(url, { headers: { referer: 'https://c.y.qq.com/', host: 'c.y.qq.com' }, params: req.query }).then((response) => { var ret = response.data if (typeof ret === 'string') { var reg = /^\w+\(({[^()]+})\)$/ var matches = ret.match(reg) if (matches) { ret = JSON.parse(matches[1]) } } res.json(ret) }).catch((e) => { console.log(e) }) }) app.use('/api', apiRoutes) var compiler = webpack(webpackConfig) // 啟動 webpack-dev-middleware,將 編譯後的檔案暫存到記憶體中 var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, quiet: true }) // 啟動 webpack-hot-middleware,也就是我們常說的 Hot-reload var hotMiddleware = require('webpack-hot-middleware')(compiler, { log: () => {} }) // force page reload when html-webpack-plugin template changes compiler.plugin('compilation', function (compilation) { compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() }) }) // 將 proxyTable 中的請求配置掛在到啟動的 express 服務上 Object.keys(proxyTable).forEach(function (context) { var options = proxyTable[context] if (typeof options === 'string') { options = { target: options } } app.use(proxyMiddleware(options.filter || context, options)) }) // 使用 connect-history-api-fallback 匹配資源,如果不匹配就可以重定向到指定地址 app.use(require('connect-history-api-fallback')()) // 將暫存到記憶體中的 webpack 編譯後的檔案掛在到 express 服務上 app.use(devMiddleware) // 將 Hot-reload 掛在到 express 服務上 app.use(hotMiddleware) // 拼接 static 資料夾的靜態資源路徑 var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) // 為靜態資源提供響應服務 app.use(staticPath, express.static('./static')) var uri = 'http://localhost:' + port var _resolve var readyPromise = new Promise(resolve => { _resolve = resolve }) console.log('> Starting dev server...') devMiddleware.waitUntilValid(() => { console.log('> Listening at ' + uri + '\n') // 如果不是測試環境,自動開啟瀏覽器並跳到我們的開發地址 if (autoOpenBrowser && process.env.NODE_ENV !== 'testing') { opn(uri) } _resolve() }) // 讓我們這個 express 服務監聽 port 的請求,並且將此服務作為 dev-server.js 的介面暴露 var server = app.listen(port) module.exports = { ready: readyPromise, close: () => { server.close() } }
2、build/webpack.base.conf.js
var path = require('path')
var utils = require('./utils')
var config = require('../config')
// .vue 檔案配置 loader 及工具 (autoprefixer)
var vueLoaderConfig = require('./vue-loader.conf')
// 拼接我們的工作區路徑為一個絕對路徑的函式
function resolve(dir) {
return path.join(__dirname, '..', dir)
}
// webpack 配置,輸入、輸出、模組、外掛
module.exports = {
entry: {
app: './src/main.js'
},
output: {
// 編譯輸出的根路徑
path: config.build.assetsRoot,
// 編譯輸出的檔名
filename: '[name].js',
// 正式釋出環境下編譯輸出的釋出路徑
publicPath: process.env.NODE_ENV === 'production'
? config.build.assetsPublicPath
: config.dev.assetsPublicPath
},
resolve: {
// 自動補全的副檔名
extensions: ['.js', '.vue', '.json'],
// 預設路徑代理,即起別名,例如 import Vue from 'vue',會自動到 'vue/dist/vue.common.js'中尋找
alias: {
'@': resolve('src'),
'common': resolve('src/common'),
'components': resolve('src/components'),
'base': resolve('src/base'),
'api': resolve('src/api')
}
},
module: {
rules: [
// 需要處理的檔案及使用的 loader
{
test: /\.(js|vue)$/,
loader: 'eslint-loader',
enforce: 'pre',
include: [resolve('src'), resolve('test')],
options: {
// eslint 程式碼檢查配置工具
formatter: require('eslint-friendly-formatter')
}
},
{
test: /\.vue$/,
loader: 'vue-loader',
options: vueLoaderConfig
},
{
test: /\.js$/,
loader: 'babel-loader',
include: [resolve('src'), resolve('test')]
},
{
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
},
{
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url-loader',
options: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}
]
}
}
3、build/webpack.dev.conf.js
// 使用一些小工具
var utils = require('./utils')
var webpack = require('webpack')
// 同樣的使用了 config/index.js 的預設配置
var config = require('../config')
// 使用 webpack 配置合併外掛
var merge = require('webpack-merge')
// 載入 webpack.base.conf
var baseWebpackConfig = require('./webpack.base.conf')
// 使用 html-webpack-plugin 外掛,這個外掛可以幫我們自動生成 html 並且注入到 .html 檔案中
var HtmlWebpackPlugin = require('html-webpack-plugin')
// 一個友好的錯誤提示外掛
var FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin')
// 將 Hol-reload 相對路徑新增到 webpack.base.conf 的 對應 entry 前
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
// 將我們 webpack.dev.conf.js 的配置和 webpack.base.conf.js 的配置合併
module.exports = merge(baseWebpackConfig, {
module: {
// 使用 styleLoaders
rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// 使用 #eval-source-map 模式作為開發工具,此配置可參考 DDFE 往期文章詳細瞭解
devtool: '#cheap-module-eval-source-map',
plugins: [
// definePlugin 接收字串插入到程式碼當中, 所以你需要的話可以寫上 JS 的字串
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// definePlugin 接收字串插入到程式碼當中, 所以你需要的話可以寫上 JS 的字串
// HotModule 外掛在頁面進行變更的時候只會重回對應的頁面模組,不會重繪整個 html 檔案
new webpack.HotModuleReplacementPlugin(),
// 遇到報錯,跳過還行,並提示錯誤資訊
new webpack.NoEmitOnErrorsPlugin(),
// 將 index.html 作為入口,注入 html 程式碼後生成 index.html檔案
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
}),
// 使用這個外掛,更好的輸出錯誤資訊
new FriendlyErrorsPlugin()
]
})
4、build/webpack.prod.conf.js
var path = require('path')
var utils = require('./utils')
var webpack = require('webpack')
var config = require('../config')
// 載入 webpack 配置合併工具
var merge = require('webpack-merge')
// 載入 webpack.base.conf.js檔案
var baseWebpackConfig = require('./webpack.base.conf')
var CopyWebpackPlugin = require('copy-webpack-plugin')
// 一個可以插入 html 並且建立新的 .html 檔案的外掛
var HtmlWebpackPlugin = require('html-webpack-plugin')
// 一個 webpack 擴充套件,可以提取一些程式碼並且將它們和檔案分離開
// 如果我們想將 webpack 打包成一個檔案 css js 分離開,那我們需要這個外掛
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin')
var env = config.build.env
// 合併 webpack.base.conf.js
var webpackConfig = merge(baseWebpackConfig, {
module: {
// 使用的 loader
rules: utils.styleLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
// 是否使用 #source-map 開發工具,更多資訊可以檢視 DDFE 往期文章
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
// 編譯輸出目錄
path: config.build.assetsRoot,
// 編譯輸出檔名
// 我們可以在 hash 後加 :6 決定使用幾位 hash 值
filename: utils.assetsPath('js/[name].[chunkhash].js'),
// 沒有指定輸出名的檔案輸出的檔名
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
plugins: [
// definePlugin 接收字串插入到程式碼當中, 所以你需要的話可以寫上 JS 的字串
new webpack.DefinePlugin({
'process.env': env
}),
// 壓縮 js (同樣可以壓縮 css)
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
},
sourceMap: true
}),
// 將 css 檔案分離出來
new ExtractTextPlugin({
filename: utils.assetsPath('css/[name].[contenthash].css')
}),
// Compress extracted CSS. We are using this plugin so that possible
// duplicated CSS from different components can be deduped.
new OptimizeCSSPlugin({
cssProcessorOptions: {
safe: true
}
}),
// 輸入輸出的 .html 檔案
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
// 是否注入 html
inject: true,
// 壓縮的方式
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
},
chunksSortMode: 'dependency'
}),
// 沒有指定輸出檔名的檔案輸出的靜態檔名
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// 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
)
}
}),
// 沒有指定輸出檔名的檔案輸出的靜態檔名
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
}),
// copy custom static assets
new CopyWebpackPlugin([
{
from: path.resolve(__dirname, '../static'),
to: config.build.assetsSubDirectory,
ignore: ['.*']
}
])
]
})
// 開啟 gzip 的情況下使用下方的配置
if (config.build.productionGzip) {
// 載入 compression-webpack-plugin 外掛
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
// 使用 compression-webpack-plugin 外掛進行壓縮
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
if (config.build.bundleAnalyzerReport) {
// 懶載入外掛
var BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin
webpackConfig.plugins.push(new BundleAnalyzerPlugin())
}
module.exports = webpackConfig
5、build/build.js
// 檢查 Node 和 npm 版本
require('./check-versions')()
// 當node無法判斷是本地還是線上時,這裡預設寫上線上
process.env.NODE_ENV = 'production'
// 一個很好看的 loading 外掛
var ora = require('ora')
var rm = require('rimraf')
var path = require('path')
var chalk = require('chalk')
var webpack = require('webpack')
var config = require('../config')
// 載入 webpack.prod.conf檔案
var webpackConfig = require('./webpack.prod.conf')
// 使用 ora 打印出 loading + log
var spinner = ora('building for production...')
// 開始 loading 動畫
spinner.start()
// 刪除打包後的資料夾,重新生成打包後的檔案
rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => {
if (err) throw err
// 開始 webpack 的編譯
webpack(webpackConfig, function (err, stats) {
// 編譯成功的回撥函式
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n\n')
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'
))
})
})
6、config/index.js
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
// production 環境
build: {
// 使用 config/prod.env.js 中定義的編譯環境
env: require('./prod.env'),
port: 9000,
index: path.resolve(__dirname, '../dist/index.html'),
// 編譯輸出的靜態資源根路徑
assetsRoot: path.resolve(__dirname, '../dist'),
// 編譯輸出的二級目錄
assetsSubDirectory: 'static',
// 編譯釋出上線路徑的根目錄,可配置為資源伺服器域名或 CDN 域名
assetsPublicPath: '',
// 是否開啟 cssSourceMap
productionSourceMap: true,
// 是否開啟 gzip
productionGzip: false,
// 需要使用 gzip 壓縮的副檔名
productionGzipExtensions: ['js', 'css'],
// 外掛叫做bundleAnalyzerReport,上面有幾行註釋,講的是隻要在打包的時候輸入npm run build --report,就可以在打包的同時檢視每個打包生成的js,裡面的庫的構成情況,方便我們進行相關的刪減,比如有的庫太大了,是否可以自己實現,有的庫是否有必要,可否刪除之類
bundleAnalyzerReport: process.env.npm_config_report
},
// 使用 config/dev.env.js 中定義的編譯環境
dev: {
env: require('./dev.env'),
// 執行測試頁面的埠
port: 8080,
// 是否自動開啟瀏覽器
autoOpenBrowser: true,
// 編譯輸出的二級目錄
assetsSubDirectory: 'static',
// 編譯釋出上線路徑的根目錄,可配置為資源伺服器域名或 CDN 域名
assetsPublicPath: '/',
// 需要 proxyTable 代理的介面(可跨域)
proxyTable: {},
// 是否開啟 cssSourceMap
cssSourceMap: false
}
}
四、小結
上述就是vue-cli的主要檔案詳解了,由於涉及的東西比較多,有一些我也不是很熟悉,只能對大家做一個簡單的解釋。至於裡面具體模組,有興趣的可以自己去Google。
如果,註釋或者言論有不當、錯誤之處,還請童鞋們之處。以免誤導他人。 ps: 喜歡我部落格的朋友,可以關注一下,我們溝通技術互相進步!