写在开头
作为一个用过ng,vue的开发者,不玩玩React不好面试啊,万一面试官问,你用了ng,vue怎么不用React呢?那不是尴尬了。
本着都试试的想法,而正好有一个应用正好想要重写(原来用的vue1.0),于是开始了React之路。
本文内容
- webpack2与webpack1的不同
- 项目结构配置
- webpack配置
- 资源服务器配置
如果你熟悉vue脚手架的webpack配置,或者熟悉webpack配置,那么你没必要看下去了。下面介绍的就是vue脚手架webpack配置。
webpack2
为了简单快速的搭建好webpack环境,直接从vue的配置复制过来修修改改,直接开始跑了。结果是可预见的,因为vue中使用的webpack 1.X版本,而最新下载的包已经是2.X版本。于是根据错误提示,首先发现webpack的loader配置已经发现了改变。其次发现不再支持除了官方定义的属性。
通过文档Migrating from v1 to v2
- 可发现
module
下loaders
属性改为了rules
,通过在rules
属性添加rule
对象,配置rule.use
来指定loader
,也可使用rule.loader
对象属性,文档介绍说loader
属性是use
属性的快照。在一个rule
中使用多个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
则将会收到警告。 -
loader
名称需要手动添加-loader
,也可以通过下面这种方式继续支持,只是不推荐。
resolveLoader: {
moduleExtensions: ["-loader"]
}
- 不在需要
json-loader
- 支持解析 import 和 exports 关键字了,不再需要 babel 对上面两个关键字进行编译。
更多更新请查看Migrating from v1 to v2
项目结构
+ react 项目目录
+ assets 静态文件目录
+ build webpack配置目录
- build.js 项目发布脚本
- config.js 项目基本配置文件
- dev-server.js 热加载脚本
- dev-server.js 启动资源服务器脚本
- webpack.base.conf.js webpack共用配置
- webpack.dev.conf.js 开发环境配置
- webpack.build.conf.js 项目发布配置
+ node_modules
+ src 项目开发目录
+ conponents 组件目录
+ styles 样式组件目录
- main.js 入口文件
+ index.html HTML模板文件,供html-webpack-plugin插件使用
+ package.json
webpack配置
基础配置
// config.js
const path = require('path')
let config = {
root: path.join(__dirname, '../'), // 项目根目录
assets: path.join(__dirname, '../assets'), // 静态文件目录
assetsDirectory: 'assets', // 静态文件目录名称
publicPath: '/', // webpack output.publicPath,网页中URL与本地文件的相对路径
dist: path.join(__dirname, '../dist'), // 发布文件输出文件夹
index: path.join(__dirname, '../dist/index.html') // 发布时HTML输出文件路径
}
module.exports = {
dev: Object.assign({}, config, {
port: 3333, // 资源服务器端口
proxy: { // 双服务器开发时,代理请求到后端服务器,如在浏览器请求http://loaclhost:3333/api/users则会请求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/
]
}]
}
}
开发环境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) {
// 这里在每个`entry`前添加./build/dev-client以接受webpack发出的事件,并处理
baseConfig.entry[name] = ['./build/dev-client'].concat(baseConfig.entry[name])
})
module.exports = merge(baseConfig, {
devtool: '#eval-source-map',
plugins: [
// 在前端页面中判断运行环境
new webpack.DefinePlugin({
'process.env': {NODE_ENV: '"development"'}
}),
new webpack.HotModuleReplacementPlugin(),
// 在webpack 2中使用NoErrorsPlugin会有警告提示
new webpack.NoEmitOnErrorsPlugin(),
// 读取HTML模板文件,并输出HTML文件,开发环境实际输出到内存中
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
})
资源服务器配置
// 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)
// 开发环境,webpack不会把内容保存到本地,会储存在内存中
let devOpts = {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true
}
}
// 设置代理
Object.keys(proxyMap).forEach((key) => app.use(proxyMiddleware(key, proxyMap[key])))
let hotServer = hotMiddleware(compiler)
// 文件发生变化,发出重新加载事件
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')
// 处理热加载插件发出的事件,重新加载页面
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})
写在最后
虽然基本上所有代码复制于vue,不过不知谁不是说了吗——没有复制,哪来创新呢。表面上是一个复制的过程,实际却是一次实践的过程,在这个过程中对如何自定义配置webpack,webpack的插件等有了更多的了解。
如果有幸被你看到这篇文章,请不要吐槽。