这是一个用来练手的小项目,使用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
启动本地开发服务器 & 后端服务器