學習鏈接:
http://blog.csdn.net/hongchh/article/details/55113751
https://segmentfault.com/a/1190000009356434
http://www.jianshu.com/p/42e11515c10f
主要分析開發(dev)和構建(build)兩個過程涉及到的文件,故下面文件結構僅列出相應的內容。
├─build │ ├─build.js │ ├─check-versions.js │ ├─dev-client.js │ ├─dev-server.js │ ├─utils.js │ ├─vue-loader.conf.js │ ├─webpack.base.conf.js │ ├─webpack.dev.conf.js │ ├─webpack.prod.conf.js │ └─webpack.test.conf.js ├─config │ ├─dev.env.js │ ├─index.js │ ├─prod.env.js │ └─test.env.js ├─... └─package.json
package.json
關注scripts字段的內容:
"scripts": { "dev": "node build/dev-server.js", "build": "node build/build.js", "lint": "eslint --ext .js,.vue src" },
直接看”dev”和”build”。運行”npm run dev”的時候執行的是build/dev-server.js文件,運行”npm run build”的時候執行的是build/build.js文件,我們可以從這兩個文件開始進行代碼閱讀分析。
build文件夾
build/dev-server.js
首先來看執行”npm run dev”時候最先執行的build/dev-server.js文件。該文件主要完成下面幾件事情:
- 檢查node和npm的版本
- 引入相關插件和配置
- 創建express服務器和webpack編譯器
- 配置開發中間件(webpack-dev-middleware)和熱重載中間件(webpack-hot-middleware)
- 掛載代理服務和中間件
- 配置靜態資源
- 啟動服務器監聽特定端口(8080)
- 自動打開瀏覽器並打開特定網址(localhost:8080)
說明: express服務器提供靜態文件服務,不過它還使用了http-proxy-middleware,一個http請求代理的中間件。前端開發過程中需要使用到后台的API的話,可以通過配置proxyTable來將相應的后台請求代理到專用的API服務器。
require('./check-versions')() /*檢查NodeJS和npm的版本*/ var config = require('../config') // 獲取配置 // 如果Node的環境變量中沒有設置當前的環境(NODE_ENV),則使用config中的配置作為當前的環境 if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV) // 一個可以調用默認軟件打開網址、圖片、文件等內容的插件 // 這里用它來調用默認瀏覽器打開dev-server監聽的端口,例如:localhost:8080 var path = require('path') var express = require('express') var webpack = require('webpack') var opn = require('opn') // 一個express中間件,用於將http請求代理到其他服務器 // 例:localhost:8080/api/xxx --> localhost:3000/api/xxx // 這里使用該插件可以將前端開發中涉及到的請求代理到API服務器上,方便與服務器對接 var proxyMiddleware = require('http-proxy-middleware') // 引入相應的 webpack 配置 var webpackConfig = require('./webpack.dev.conf') // dev-server 監聽的端口,默認為config.dev.port設置的端口,即8080 // default port where dev server listens for incoming traffic var port = process.env.PORT || config.dev.port // Define HTTP proxies to your custom API backend // https://github.com/chimurai/http-proxy-middleware // 定義 HTTP 代理表,代理到 API 服務器 var proxyTable = config.dev.proxyTable // 創建1個 express 實例 var app = express() // 數據mock var appData = require('../static/data.json')//在根目錄下放置的一個data.json文件,用以模擬請求的數據 var seller = appData.seller//獲取data中的數據對象 var goods = appData.goods var ratings = appData.ratings var apiRoutes = express.Router()//創建一個路由實例 apiRoutes.get('/seller', function(req, res) {//獲取數據 res.json({ errno: 0, data: seller }) }) apiRoutes.get('/goods', function(req, res) { res.json({ errno: 0, data: goods }) }) apiRoutes.get('/ratings', function(req, res) { res.json({ errno: 0, data: ratings }) }) app.use('/api', apiRoutes)// express使用api // 根據webpack配置文件創建Compiler對象 var compiler = webpack(webpackConfig) // webpack-dev-middleware使用compiler對象來對相應的文件進行編譯和綁定 // 編譯綁定后將得到的產物存放在內存中而沒有寫進磁盤 // 將這個中間件交給express使用之后即可訪問這些編譯后的產品文件 var devMiddleware = require('webpack-dev-middleware')(compiler, { publicPath: webpackConfig.output.publicPath, stats: { colors: true, chunks: false } }) // webpack-hot-middleware,用於實現熱重載功能的中間件 var hotMiddleware = require('webpack-hot-middleware')(compiler) // force page reload when html-webpack-plugin template changes // 當html-webpack-plugin提交之后通過熱重載中間件發布重載動作使得頁面重載 compiler.plugin('compilation', function(compilation) { compilation.plugin('html-webpack-plugin-after-emit', function(data, cb) { hotMiddleware.publish({ action: 'reload' }) cb() }) }) // 將 proxyTable 中的代理請求配置掛在到express服務器上 // proxy api requests Object.keys(proxyTable).forEach(function(context) { var options = proxyTable[context] if (typeof options === 'string') { options = { target: options } } app.use(proxyMiddleware(context, options)) }) // handle fallback for HTML5 history API // 重定向不存在的URL,常用於SPA app.use(require('connect-history-api-fallback')()) // serve webpack bundle output // 使用webpack開發中間件 // 即將webpack編譯后輸出到內存中的文件資源掛到express服務器上 app.use(devMiddleware) // 將熱重載中間件掛在到express服務器上 // enable hot-reload and state-preserving // compilation error display app.use(hotMiddleware) // serve pure static assets// 靜態資源的路徑// 將靜態資源掛到express服務器上 var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory) app.use(staticPath, express.static('./static')) // 啟動express服務器並監聽相應的端口(8080) module.exports = app.listen(port, function(err) { if (err) { console.log(err) return }
// 應用的地址信息,例如:http://localhost:8080 var uri = 'http://localhost:' + port console.log('Listening at ' + uri + '\n') // 如果符合自動打開瀏覽器的條件,則通過opn插件調用系統默認瀏覽器打開對應的地址uri // when env is testing, don't need open it if (process.env.NODE_ENV !== 'testing') { opn(uri) } })
build/webpack.base.conf.js
從代碼中看到,dev-server使用的webpack配置來自build/webpack.dev.conf.js文件(測試環境下使用的是build/webpack.prod.conf.js,這里暫時不考慮測試環境)。而build/webpack.dev.conf.js中又引用了webpack.base.conf.js,所以先分析webpack.base.conf.js。
webpack.base.conf.js主要完成了下面這些事情:
- 配置webpack編譯入口
- 配置webpack輸出路徑和命名規則
- 配置模塊resolve規則
- 配置不同類型模塊的處理規則
說明: 這個配置里面只配置了.js、.vue、圖片、字體等幾類文件的處理規則,如果需要處理其他文件可以在module.rules里面配置。
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')// 給出正確的絕對路徑 var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
module.exports = {
entry: {// 配置webpack編譯入口
app: './src/main.js'
},
output: {// 配置webpack輸出路徑和命名規則
path: config.build.assetsRoot,// webpack輸出的目標文件夾路徑(例如:/dist)
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,// webpack編譯輸出的發布路徑
filename: '[name].js' // webpack輸出bundle文件命名格式
},
resolve: {// 配置模塊resolve的規則
extensions: ['', '.js', '.vue'],// 自動resolve的擴展名
fallback: [path.join(__dirname, '../node_modules')],
alias: {// 創建路徑別名,有了別名之后引用模塊更方便,例如 // import Vue from 'vue/dist/vue.common.js'可以寫成 import Vue from 'vue' 'vue$': 'vue/dist/vue.common.js',
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components'),
'common': path.resolve(__dirname, '../src/common'),
'img': path.resolve(__dirname, '../resource/img')
}
},
resolveLoader: {
fallback: [path.join(__dirname, '../node_modules')]
},
module: {// 配置不同類型模塊的處理規則
preLoaders: [{
test: /\.vue$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}, {
test: /\.js$/,// 對所有.vue文件使用eslint
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}],
loaders: [{
test: /\.vue$/,
loader: 'vue'
}, {
test: /\.js$/,// 對src和test文件夾下的.js文件使用babel-loader
loader: 'babel',
include: projectRoot,
exclude: /node_modules/
}, {
test: /\.json$/,
loader: 'json'
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}, {// 對字體資源文件使用url,query.name指明了輸出的命名規則
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}]
},
eslint: {
formatter: require('eslint-friendly-formatter')
},
vue: {
loaders: utils.cssLoaders({
sourceMap: useCssSourceMap
}),
postcss: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
]
}
}
build/webpack.dev.conf.js
接下來看webpack.dev.conf.js,這里面在webpack.base.conf的基礎上增加完善了開發環境下面的配置,主要包括下面幾件事情:
- 將hot-reload相關的代碼添加到entry chunks
- 合並基礎的webpack配置
- 使用styleLoaders
- 配置Source Maps
- 配置webpack插件
var config = require('../config')
var webpack = require('webpack')
// 一個可以合並數組和對象的插件 var merge = require('webpack-merge')
var utils = require('./utils')
var baseWebpackConfig = require('./webpack.base.conf')
// 一個用於生成HTML文件並自動注入依賴文件(link/script)的webpack插件 var HtmlWebpackPlugin = require('html-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
// 合並基礎的webpack配置
module.exports = merge(baseWebpackConfig, {
// 配置樣式文件的處理規則,使用styleLoaders
module: {
loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// eval-source-map is faster for development// 配置Source Maps
devtool: '#eval-source-map',
plugins: [// 配置webpack插件 new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
})
build/webpack.prod.conf.js
構建的時候用到的webpack配置來自webpack.prod.conf.js,該配置同樣是在webpack.base.conf基礎上的進一步完善。主要完成下面幾件事情:
- 合並基礎的webpack配置
- 使用styleLoaders
- 配置webpack的輸出
- 配置webpack插件
- gzip模式下的webpack插件配置
- webpack-bundle分析
說明: webpack插件里面多了丑化壓縮代碼以及抽離css文件等插件。
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')// 用於從webpack生成的bundle中提取文本到特定文件中的插件 // 可以抽取出css,js文件將其與webpack輸出的bundle分離 var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {// 合並基礎的webpack配置
module: {
loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {// 配置webpack的輸出
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
vue: {
loaders: utils.cssLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({// 抽離css文件 'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
// extract css into its own file
new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
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
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
module.exports = webpackConfig