這是一個用來練手的小項目,使用webpack + vue + express 實現了簡單的圖片讀取、上傳預覽功能。javascript
挖個坑記錄下這幾天開發的過程,業務代碼其實都還好, express、node都只使用了簡單的路由、文件流的功能, webpack配置是最大的痛點。css
本項目啓動了兩個http服務:
3000端口用戶本地開發,配合webpack完成實時打包熱替換等功能。
3001端口提供api、靜態資源路由。前端
webpack配置部分參考了 vue-cli https://github.com/vuejs/vue-clivue
webpack的基礎屬性就再也不贅述了, 這裏直接講一些關鍵部分。java
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插件自動生成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-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")] }
咱們的本地開發服務器跟後端服務器是跨域的(端口號不一致), 經過 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 讀取圖片列表, 上傳圖片,刪除圖片。 設置了首頁路由,靜態資源路由。
(待續)
實現基本的相冊功能用到了 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 }) }) })
(待續)
以前用過 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
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
啓動本地開發服務器 & 後端服務器