写在开头
作为一个用过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配置已经发现了改变。其次发现不再支持除了官方定义的属性。
- 可发现
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的插件等有了更多的了解。
如果有幸被你看到这篇文章,请不要吐槽。