磨刀不誤砍材工,React項(xiàng)目的webpack配置

寫在開頭

作為一個(gè)用過ng,vue的開發(fā)者,不玩玩React不好面試啊,萬一面試官問,你用了ng,vue怎么不用React呢?那不是尷尬了。
本著都試試的想法,而正好有一個(gè)應(yīng)用正好想要重寫(原來用的vue1.0),于是開始了React之路。

本文內(nèi)容

  1. webpack2與webpack1的不同
  2. 項(xiàng)目結(jié)構(gòu)配置
  3. webpack配置
  4. 資源服務(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)不再支持除了官方定義的屬性。

通過文檔Migrating from v1 to v2

  • 可發(fā)現(xiàn) moduleloaders屬性改為了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)

  • loaderquery也改成了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)不要吐槽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容