磨刀不误砍材工,React项目的webpack配置

写在开头

作为一个用过ng,vue的开发者,不玩玩React不好面试啊,万一面试官问,你用了ng,vue怎么不用React呢?那不是尴尬了。
本着都试试的想法,而正好有一个应用正好想要重写(原来用的vue1.0),于是开始了React之路。

本文内容

  1. webpack2与webpack1的不同
  2. 项目结构配置
  3. webpack配置
  4. 资源服务器配置

如果你熟悉vue脚手架的webpack配置,或者熟悉webpack配置,那么你没必要看下去了。下面介绍的就是vue脚手架webpack配置。

webpack2

为了简单快速的搭建好webpack环境,直接从vue的配置复制过来修修改改,直接开始跑了。结果是可预见的,因为vue中使用的webpack 1.X版本,而最新下载的包已经是2.X版本。于是根据错误提示,首先发现webpack的loader配置已经发现了改变。其次发现不再支持除了官方定义的属性。

通过文档Migrating from v1 to v2

  • 可发现 moduleloaders属性改为了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)

  • loaderquery也改成了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的插件等有了更多的了解。

如果有幸被你看到这篇文章,请不要吐槽。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,911评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,014评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,129评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,283评论 1 264
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,159评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,161评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,565评论 3 382
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,251评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,531评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,619评论 2 310
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,383评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,255评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,624评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,916评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,199评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,553评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,756评论 2 335

推荐阅读更多精彩内容