寫在開頭
作為一個(gè)用過ng,vue的開發(fā)者,不玩玩React不好面試啊,萬一面試官問,你用了ng,vue怎么不用React呢?那不是尷尬了。
本著都試試的想法,而正好有一個(gè)應(yīng)用正好想要重寫(原來用的vue1.0),于是開始了React之路。
本文內(nèi)容
- webpack2與webpack1的不同
- 項(xiàng)目結(jié)構(gòu)配置
- webpack配置
- 資源服務(wù)器配置
如果你熟悉vue腳手架的webpack配置,或者熟悉webpack配置,那么你沒必要看下去了。下面介紹的就是vue腳手架webpack配置。
webpack2
為了簡(jiǎn)單快速的搭建好webpack環(huán)境,直接從vue的配置復(fù)制過來修修改改,直接開始跑了。結(jié)果是可預(yù)見的,因?yàn)関ue中使用的webpack 1.X版本,而最新下載的包已經(jīng)是2.X版本。于是根據(jù)錯(cuò)誤提示,首先發(fā)現(xiàn)webpack的loader配置已經(jīng)發(fā)現(xiàn)了改變。其次發(fā)現(xiàn)不再支持除了官方定義的屬性。
- 可發(fā)現(xiàn)
module
下loaders
屬性改為了rules
,通過在rules
屬性添加rule
對(duì)象,配置rule.use
來指定loader
,也可使用rule.loader
對(duì)象屬性,文檔介紹說loader
屬性是use
屬性的快照。在一個(gè)rule
中使用多個(gè)loader
,webpack將從右向左的順序使用loader
Loaders can be chained by passing multiple loaders, which will be applied from right to left (last to first configured)
-
loader
的query
也改成了options
,如果還是使用query
則將會(huì)收到警告。 -
loader
名稱需要手動(dòng)添加-loader
,也可以通過下面這種方式繼續(xù)支持,只是不推薦。
resolveLoader: {
moduleExtensions: ["-loader"]
}
- 不在需要
json-loader
- 支持解析 import 和 exports 關(guān)鍵字了,不再需要 babel 對(duì)上面兩個(gè)關(guān)鍵字進(jìn)行編譯。
更多更新請(qǐng)查看Migrating from v1 to v2
項(xiàng)目結(jié)構(gòu)
+ react 項(xiàng)目目錄
+ assets 靜態(tài)文件目錄
+ build webpack配置目錄
- build.js 項(xiàng)目發(fā)布腳本
- config.js 項(xiàng)目基本配置文件
- dev-server.js 熱加載腳本
- dev-server.js 啟動(dòng)資源服務(wù)器腳本
- webpack.base.conf.js webpack共用配置
- webpack.dev.conf.js 開發(fā)環(huán)境配置
- webpack.build.conf.js 項(xiàng)目發(fā)布配置
+ node_modules
+ src 項(xiàng)目開發(fā)目錄
+ conponents 組件目錄
+ styles 樣式組件目錄
- main.js 入口文件
+ index.html HTML模板文件,供html-webpack-plugin插件使用
+ package.json
webpack配置
基礎(chǔ)配置
// config.js
const path = require('path')
let config = {
root: path.join(__dirname, '../'), // 項(xiàng)目根目錄
assets: path.join(__dirname, '../assets'), // 靜態(tài)文件目錄
assetsDirectory: 'assets', // 靜態(tài)文件目錄名稱
publicPath: '/', // webpack output.publicPath,網(wǎng)頁中URL與本地文件的相對(duì)路徑
dist: path.join(__dirname, '../dist'), // 發(fā)布文件輸出文件夾
index: path.join(__dirname, '../dist/index.html') // 發(fā)布時(shí)HTML輸出文件路徑
}
module.exports = {
dev: Object.assign({}, config, {
port: 3333, // 資源服務(wù)器端口
proxy: { // 雙服務(wù)器開發(fā)時(shí),代理請(qǐng)求到后端服務(wù)器,如在瀏覽器請(qǐng)求http://loaclhost:3333/api/users則會(huì)請(qǐng)求http://localhost:3000/api/users
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
},
'/img': {
target: 'http://localhost:3000',
changeOrigin: true
},
'/offline.manifest': {
target: 'http://localhost:3000',
changeOrigin: true
}
}}),
pro: Object.assign({}, config, {
})
}
公共配置
// webpack.base.conf.js
const path = require('path')
const config = require('./config')
const projectRoot = path.resolve(__dirname, '../')
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.pro.root,
publicPath: process.env.NODE_ENV === 'production' ? config.pro.publicPath : config.dev.publicPath,
filename: '[name].js'
},
resolve: {
extensions: ['json', 'jsx', '.js'],
alias: {
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../assets'),
'components': path.resolve(__dirname, '../src/components')
},
modules: [
path.resolve(__dirname, '../node_modules')
]
},
module: {
rules: [{
test: /\.js$/,
use: [
{
loader: 'babel-loader',
options: {
presets: [
[
"es2015",
{
"modules": false
}
],
"react",
"stage-0"
]
}
},
'eslint-loader'
],
include: [
projectRoot
],
exclude: [
/node_modules/
]
}]
}
}
開發(fā)環(huán)境webpack配置
// webpack.dev.conf.js
const webpack = require('webpack')
const merge = require('webpack-merge')
const baseConfig = require('./webpack.base.conf')
const HtmlWebpackPlugin = require('html-webpack-plugin')
Object.keys(baseConfig.entry).forEach(function (name) {
// 這里在每個(gè)`entry`前添加./build/dev-client以接受webpack發(fā)出的事件,并處理
baseConfig.entry[name] = ['./build/dev-client'].concat(baseConfig.entry[name])
})
module.exports = merge(baseConfig, {
devtool: '#eval-source-map',
plugins: [
// 在前端頁面中判斷運(yùn)行環(huán)境
new webpack.DefinePlugin({
'process.env': {NODE_ENV: '"development"'}
}),
new webpack.HotModuleReplacementPlugin(),
// 在webpack 2中使用NoErrorsPlugin會(huì)有警告提示
new webpack.NoEmitOnErrorsPlugin(),
// 讀取HTML模板文件,并輸出HTML文件,開發(fā)環(huán)境實(shí)際輸出到內(nèi)存中
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
})
資源服務(wù)器配置
// dev-server.js
const express = require('express')
const webpack = require('webpack')
// 代理中間件
const proxyMiddleware = require('http-proxy-middleware')
// 熱加載中間件
const devMiddleware = require('webpack-dev-middleware')
const hotMiddleware = require("webpack-hot-middleware")
// history API處理中間件
const history = require('connect-history-api-fallback')
const webpackConfig = require('./webpack.dev.conf')
const config = require('./config')
const port = process.env.PORT || config.dev.port
const proxyMap = config.dev.proxy
const app = express()
const compiler = webpack(webpackConfig)
// 開發(fā)環(huán)境,webpack不會(huì)把內(nèi)容保存到本地,會(huì)儲(chǔ)存在內(nèi)存中
let devOpts = {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true
}
}
// 設(shè)置代理
Object.keys(proxyMap).forEach((key) => app.use(proxyMiddleware(key, proxyMap[key])))
let hotServer = hotMiddleware(compiler)
// 文件發(fā)生變化,發(fā)出重新加載事件
compiler.plugin('compilation', (compilation) => {
compilation.plugin('html-webpack-plugin-after-emit', (data, cb) => {
hotServer.publish({ action: 'reload' })
cb()
})
})
app.use(history())
app.use(devMiddleware(compiler, devOpts))
app.use(hotServer)
app.use('/assets', express.static(config.dev.assets))
app.listen(port, (err) => {
console.log(err ? err : 'Listening at http://localhost:' + port + '\n')
})
事件處理
// dev-client.js
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
// 處理熱加載插件發(fā)出的事件,重新加載頁面
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
寫在最后
雖然基本上所有代碼復(fù)制于vue,不過不知誰不是說了嗎——沒有復(fù)制,哪來創(chuàng)新呢。表面上是一個(gè)復(fù)制的過程,實(shí)際卻是一次實(shí)踐的過程,在這個(gè)過程中對(duì)如何自定義配置webpack,webpack的插件等有了更多的了解。
如果有幸被你看到這篇文章,請(qǐng)不要吐槽。