webpack+vue+express搭建小型相冊項目

這是一個用來練手的小項目,使用webpack + vue + express 實現了簡單的圖片讀取、上傳預覽功能。javascript

挖個坑記錄下這幾天開發的過程,業務代碼其實都還好, express、node都只使用了簡單的路由、文件流的功能, webpack配置是最大的痛點。css

源碼 https://github.com/littlewood...html

寫在前面

本項目啓動了兩個http服務:
3000端口用戶本地開發,配合webpack完成實時打包熱替換等功能。
3001端口提供api、靜態資源路由。前端

目錄結構

圖片描述

webpack

webpack配置部分參考了 vue-cli https://github.com/vuejs/vue-clivue

webpack的基礎屬性就再也不贅述了, 這裏直接講一些關鍵部分。java

webpack-dev-middlware 、 webpack-hot-middleware

webpack-dev-middlware + webpack-hot-middleware 結合爲咱們打造了自動打包、熱替換的本地服務器, 解放了咱們的F5。 爲了隔離不一樣的環境,咱們能夠寫兩套webpack配置, 根據不一樣的環境調用不一樣的配置文件 詳見webpack.dev.conf.js、webpack.prod.conf.js。node

可是須要特別注意:webpack-dev-middleware組件打包後的文件放是在內存, 因此output path不會有新文件, 開發環境下應該讀取根目錄webpack

配置以下:git

修改入口entry, 這裏entry須要添加 dev-client.js文件來實現熱替換github

entry: ['./build/dev-client.js', './project/src/index.js']

配置 publicPath

// 開發環境
  output: {
    publicPath: '/'
  }


  // 生產環境
  output: {
    publicPath: '//localhost:3001/static'
  }

開發環境下publicPath 我使用了根目錄。而生產環境這裏我用了 ‘//localhost:3001/static’, 你們這裏能夠填寫實際生產環境的靜態資源地址。

dev-client.js

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()
  }
})

webpack-dev-middleware 的publicPath須要與webpack配置裏的publicPath保持一致。

引用插件HotModuleReplacementPlugin

plugins: [
    new webpack.HotModuleReplacementPlugin()
  ]

最後咱們要新建 dev-server.js文件 啓動http服務

dev-server.js

var webpack = require('webpack')
var express = require('express')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')
var app = express()
var webpackConfig = require('./webpack.dev.conf')
var compiler = webpack(webpackConfig)
var devMiddleWare = webpackDevMiddleware(compiler, {
  publicPath: webpackConfig.output.publicPath,
  noInfo: true,
  hot: true,
  stats: {
    colors: true
  }
})


// 每當webpack打包時觸發 compilation
var hotMiddleWare = webpackHotMiddleware(compiler)
compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleWare.publish({ action: 'reload' })
    cb()
  })
})

app.use(devMiddleWare)
app.use(hotMiddleWare)

app.listen(3000, function() {
  console.log('listening on 3000')
})

webpack 流程參考文章 http://taobaofed.org/blog/201...
webpack Node API 參考文章 https://doc.webpack-china.org...

html-webpack-plugin-after-emit 是webpack的一個事件,

寫到這裏咱們的項目已經能夠在本地跑起來啦, 咱們能夠建立兩個npm命令, 方便咱們開發和打包代碼

// package.json
  scripts: {
    "dev": "cross-env NODE_ENV=dev node ./build/dev-server.js",
    "build": "webpack --config ./build/webpack.prod.conf.js",
  }

自動打開頁面

咱們可能但願在開啓服務的時候自動打開頁面

app.listen(3000, function() {
  let uri = `http://localhost:3000`
  console.log('start listening on 3000')
  opn(uri)
})

opn是一個能夠打開瀏覽器的插件。

安裝

npm install opn --save-dev

webpack-html-plugin

使用webpack-html-plugin插件自動生成html, 注意開發環境下生成的文件在根目錄

配置 webpack

plugins: [
  new HtmlWebpackPlugin({
    template: 'project/src/index.jade',
    filename: 'index.html'
  })
]

壓縮文件

咱們生產環境下須要壓縮文件, 使用webpack自帶組件 UglifyJsPlugin

plugins: [
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ]

若是使用了ES6語法,webpack 打包時可能會提示報錯, 須要配置babel。

配置babel

安裝babel-loader, 在webpack配置裏給js添加babel-loader

npm install babel-loader --save-dev

{
    test: /\.js$/,
    loader: 'babel-loader'
  }

不過僅僅這樣是不能正常工做的, 必須配置babel插件。

在根目錄裏新建.babelrc文件, 預設對應測插件。之前咱們可能安裝須要babel-presets-201五、babel-pressets-201六、babel-preset-latest 等多個插件, 將ES6.ES7轉爲ES5。 這樣有一個問題, 在支持大部分ES新特性的現代瀏覽器好比chrome上, 也作了所有的轉換。 babel新提供了一個很是強大的插件 babel-presets-env,它能夠根據不一樣的平臺和目標瀏覽器智能的轉換代碼, 僅僅轉換一些不支持的特性。

安裝 babel-presets-env

npm install babel-presets-env --save-dev

.babelrc

{
  "presets": [
    ["env", { "modules": false }]
  ]
}

配置babel以後, 瀏覽器可能會報錯:'npm run dev Cannot read property 'EventSource' of undefined'。解決辦法:在babel-loader裏配置include

{
  test: /\.js$/,
  loader: 'babel-loader',
  include:[path.resolve(__dirname,"./../project")]
}

proxyTable

咱們的本地開發服務器跟後端服務器是跨域的(端口號不一致), 經過 http-proxy-middleware 插件, 配置proxyTable能夠解決跨域問題。

配置映射表 dev-proxy.js

module.exports = {
  '/getList': {
    target: 'http://localhost:3001/',
    changeOrigin: true
  },
  '/uploadImg': {
    target: 'http://localhost:3001',
    changeOrigin: true
  },
  '/delImg': {
    target: 'http://localhost:3001',
    changeOrigin: true
  }
}

dev-server.js

var proxyMiddleware = require('http-proxy-middleware')
var config = require('./../config')
var proxyTable = config.dev.proxyTable
Object.keys(proxyTable).forEach(function(context) {
  var options = proxyTable[context]
  app.use(context, proxyMiddleware(options))
})

這樣在咱們請求的時候傳入相對路徑的api, http-proxy-middleware 就會自動幫咱們把轉發到 http://localhost:3001 啦, 跨域 不存在的~

最終配置文件

到這裏webpack配置已經所有完成, 雖然還很粗糙Orz,可是已經知足咱們的基本需求了,

webpack.dev.conf.js

const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')
var hotMiddlewareScript = 'webpack-hot-middleware/client?reload=true'
console.log()
var devConfig =  {
  entry: ['./build/dev-client.js', './project/src/index.js'],
  devtool: 'source-map',
  output: {
    path: path.resolve(__dirname, './../static/'),
    publicPath: '/',
    filename: 'bundle.js'
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.common.js'
    }
  },
  plugins: [
    new webpack.HotModuleReplacementPlugin(),
    new HtmlWebpackPlugin({
      template: 'project/src/index.jade',
      filename: 'index.html'
    })
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader',
        include:[path.resolve(__dirname,"./../project")]
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.styl$/,
        use: ['style-loader', 'css-loader','stylus-loader']
      },
      {
        test: /\.jade$/,
        use: 'jade-loader'
      },
      {
        test: /\.stylus$/,
        loader: 'stylus-loader'
      },
      {
        test: /\.vue$/,
        loader: ['vue-loader']
      }
    ]
  }
}

module.exports = devConfig

webpack.prod.conf.js

const path = require('path')
const webpack = require('webpack')
const HtmlWebpackPlugin = require('html-webpack-plugin')

var config =  {
  entry: path.resolve(__dirname, './../project/src/index.js'),
  output: {
    path: path.resolve(__dirname, './../static/'),
    publicPath: '//localhost:3001/static',
    filename: 'bundle.js'
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.common.js'
    }
  },
  plugins: [
    new HtmlWebpackPlugin({
      template: path.resolve(__dirname, './../project/src/index.jade'),
      filename: 'index.html'
    }),
    new webpack.optimize.UglifyJsPlugin({
      compress: {
        warnings: false
      }
    })
  ],
  module: {
    rules: [
      {
        test: /\.js$/,
        loader: 'babel-loader'
      },
      {
        test: /\.css$/,
        use: ['style-loader', 'css-loader']
      },
      {
        test: /\.styl$/,
        use: ['style-loader', 'css-loader','stylus-loader']
      },
      {
        test: /\.jade$/,
        use: 'jade-loader'
      },
      {
        test: /\.stylus$/,
        loader: 'stylus-loader'
      },
      {
        test: /\.vue$/,
        loader: ['vue-loader']
      }
    ]
  }
}

module.exports = config

dev-server.js

var fs = require('fs')
var path = require('path')
var opn = require('opn')
var express = require('express')
var webpack = require('webpack')
var webpackDevMiddleware = require('webpack-dev-middleware')
var webpackHotMiddleware = require('webpack-hot-middleware')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig =  require('./webpack.dev.conf')
var config = require('./../config')
var compiler = webpack(webpackConfig)
var app = express()
var proxyTable = config.dev.proxyTable

console.log('當前環境 => ' + process.env.NODE_ENV)

var devMiddleWare = webpackDevMiddleware(compiler, {
  publicPath: webpackConfig.output.publicPath,
  noInfo: true,
  hot: true,
  stats: {
    colors: true
  }
})

var hotMiddleWare = webpackHotMiddleware(compiler)

compiler.plugin('compilation', function (compilation) {
  compilation.plugin('html-webpack-plugin-after-emit', function (data, cb) {
    hotMiddleWare.publish({ action: 'reload' })
    cb()
  })
})

// set proxy
Object.keys(proxyTable).forEach(function(context) {
  var options = proxyTable[context]
  app.use(context, proxyMiddleware(options))
})

app.use(devMiddleWare)
app.use(hotMiddleWare)

app.use(express.static('static'))

devMiddleWare.waitUntilValid(() => {
  _resolve()
})

app.listen(3000, function() {
  let uri = `http://localhost:3000`
  console.log('start listening on 3000')
  opn(uri)
})

var _resolve
var readyPromise = new Promise(resolve => {
  _resolve = resolve
})

module.exports = {
  redy: readyPromise,
  close: () => {
    server.close()
  }
}

服務端代碼

服務端監聽3001端口號, 主要提供了3個api 讀取圖片列表, 上傳圖片,刪除圖片。 設置了首頁路由,靜態資源路由。

(待續)

node

實現基本的相冊功能用到了 fs 讀寫文件流, 建立目錄,刪除文件等。

上傳圖片

上傳圖片這裏採起的是form提交, 必須設置 enctype="multipart/form-data", 服務端才能獲取正確的格式。

var bodyParse = require('body-parser')
app.use(bodyParse.json())
app.post('/uploadImg', upload.single('file'), function(req, res) {
  let  item = req.file
  let extName = item.originalname.match(/(\.[^\.]+)$/)[1]
  let targetPath = path.resolve(__dirname, './../static/uploads/' + new Date().getTime()) + extName
  let is = fs.createReadStream(item.path)
  let os = fs.createWriteStream(targetPath)

  is.pipe(os)

  is.on('end', function() {
    res.json({
      url: targetPath
    })
  })
})

(待續)

使用 pm2 管理進程

以前用過 supervisor 來監重啓聽代碼。 可是最新發現pm2功能更強大,不只能夠重啓代碼, 能夠在後臺運行(不再用擔憂不當心退出進程了),操做界面也更友好!

"scripts": {
    "server": "supervisor ./server/main.js",
  }

安裝 pm2

npm install pm2 --save-dev

配置文件 process.yml

apps:
  - script : server/main.js
    watch  : ['./build/dev-server.js']
  - script : build/dev-server.js
    watch  : ['./server/main.js']
    env    :
      NODE_ENV: development
    env_production:
      NODE_ENV: production

npm 命令

package.json

"scripts": {
    "test": "echo \"Error: no test specified\" && exit 1",
    "dev": "cross-env NODE_ENV=dev node ./build/dev-server.js",
    "server": "supervisor ./server/main.js",
    "build": "webpack --config ./build/webpack.prod.conf.js",
    "preview": "node ./build/preview.js",
    "start": "pm2 start process.yml"
  }

npm install 安裝依賴

npm run dev 啓動本地開發服務器

npm run server 啓動後端服務器

npm run build 構建生產環境前端代碼

npm run preview 預覽頁面

npm start 啓動本地開發服務器 & 後端服務器

相關文章
相關標籤/搜索