webpack搭建服务端项目
参考文章: https://juejin.im/post/5cb1aabdf265da037b6101d3
前言
最近有如下一些零零碎碎的小需求,总结起来,都是页面偏向于展示,与用户交互较少。因此,选择搭建了一个多页面的服务端项目。
- 低版本app用户展示的升级页面,
- 以及其他需要作为app中一些用来帮忙实现部分功能的中间页面
整个项目采用了webpack4 + express + ejs实现。
代码地址:https://github.com/chestnut647/serverSideRendering
webpack构建流程
由于是第一次脱离框架脚手架直接写webpack配置,花费了些时间。整个思路如下:
- 构思好项目的结构
- 先简单配置entry 和output配置
- 需要支持es6【模块化】,配置babel-loader
- 需要输出html文件并直接js的自动引入,引入html-loader loader以及html-webpack-plugin插件
- 不支持ejs语法中include语法,使用ejs-html-loader
- 项目没有热更新,引入webpack-dev-middleware和webpack-hot-middleware
开始构建
介绍构建过程中的一些重点,项目初始架构如下所示:
build内文件如下,config.json用于存放一些webpack使用的常量配置
配置js入口
js入口配置,在配置entry的时候,由于项目后续可能还会增加新的需求,不能每增加一次需求页面,就要在webpack中新增entry,因此采用了glob.sync来读取js文件夹下的所有js文件。
同时在使用html-webpack-plugin时我们也是使用glob.sync来避免每次都需要新增的问题。
entry: (function(fileLists) {
let entryObj = {};
const basePath = resolve(__dirname, "../source/public/javascripts/main/");
fileLists.map((filePath) => {
const temp = filePath.split(basePath),
filename = temp[temp.length - 1].split('/').join('_').slice(1).split('.')[0];
entryObj[filename] = filePath;
});
return entryObj;
})(glob.sync(resolve(__dirname, "../source/public/javascripts/main/_*/_.js"))),
output: {
path: resolve(__dirname, `../${CONFIG.DIRC.DIST}`),
filename: `${CONFIG.DIRC.SCRIPT}/[name].bundle.js`
}
配置热更新
使用webpack-dev-middleware配合webpack-hot-middleware来实现。
- webpack-dev-middleware 用以实现文件变动自动编译
- webpack-hot-middleware实现模块热替换
webpack-dev-middleware
首先我们判断当前是否为开发环境,非开发环境的时候直接使用express方式启动项目。
当为开发环境时,我们采用webpack-dev-middleware【参数webpack.config.js生成的compile】生成的eppress中间件,访问的页面的时候就会经过webpack-dev-middleware,根据webpack.config.js里的配置,当js文件变动的时候可以自动编译。
if(isDev) {
app.use(webpackDevMiddleware(compile, {
publicPath: webpackDevConfig.output.publicPath
}))
app.use(webpackDevConfig.output.publicPath, express.static(path.join(__dirname, 'source')))
} else {
app.set('view engine', 'ejs');
app.set('views', path.join(__dirname, 'dist/views'));
app.use(virtualDirctory, express.static(path.join(__dirname, 'dist')));
}
使用自动编译打包webpack-dev-middleware带来了一个问题:直接source目录下的views文件夹来直接设置views是无法达到预期效果的。因为source下的是没有打包的ejs模板,并没有注入项目中的js、css。因此需要重写render方法
原来使用方式为:
router.get('/test1', function(req, res, next) {
res.render('test1', {
title: 'test1'
});
})
重写一个render方法:
当在开发环境下,直接通过axios去获取打包出来的ejs文件内容,再通过ejs.render渲染出来。
function render(res, filename, data) {
if(isDev) {
const localPath = `http://localhost:${packageConfig.config.port}${CONFIG.PATH.PUBLIC_PATH}/${CONFIG.DIRC.VIEW}/${filename}.ejs`;
axios.get(localPath)
.then(fileRes => {
const html = ejs.render(fileRes.data, data);
res.send(html)
})
return;
}
res.render(filename, data);
}
webpack-hot-middleware
通过上述配置,可以发现,当对入口文件中的js或者css进行修改后,webpack就会进行自动编译,但是想要获取最新代码,还是需要手动刷新浏览器,引入webpack-hot-middleware
app.use(webpackHotMiddleware(compile, {
publicPath: webpackDevConfig.output.publicPath,
}))
同时修改webpack文件中entry入口配置
entry: (function(fileLists) {
let entryObj = {};
const basePath = resolve(__dirname, "../source/public/javascripts/main/");
fileLists.map((filePath) => {
const temp = filePath.split(basePath),
filename = temp[temp.length - 1].split('/').join('_').slice(1).split('.')[0];
// entryObj[filename] = filePath; 在开发环境需要将webpack-hot-middleware添入到入口文件里
entryObj[filename] = isDev ? ['webpack-hot-middleware/client?noInfo=true&reload=true', filePath] :filePath;
});
return entryObj;
})(glob.sync(resolve(__dirname, "../source/public/javascripts/main/_*/_.js")))
通过这两步配置后,修改js文件以及css文件浏览器会自动更新。但是修改ejs模板文件,浏览器还是不能够自动更新。
在ejs模版文件的入口,增加如下代码。
- 在js中直接require模版文件,当ejs修改之后,webpack就会将其视为需要热更新的一部分
- 进行模块热替换,重新获取打包后文件的内容,替换到当前页面innerHtml上。
if(process.env.NODE_ENV === 'development') {
require('raw-loader!@views/test1.ejs')
}
if(module.hot) {
module.hot.accept();
module.hot.dispose(() => {
const axios = require('axios');
const href = window.location.href
axios.get(href).then(res => {
const template = res.data
document.body.innerHTML = template
}).catch(e => {
console.error(e)
})
})
}
当入口页面非常多的时候,你需要每个都手动添加上述代码,过于复杂,写一个简单的loader,用以给每个入口j文件增加一段上述函数。
/**
* 给项目提供views的实时更新
* 在js文件中增加 enable hot updates of view, [filename] 的注释即可
*/
module.exports = function (resource) {
const reg = /enable hot updates of view,[\s]*([^\s]+)/i;
const matchRes = resource.match(reg);
if(matchRes) {
const filename = matchRes[1];
console.log(`给文件${filename}.js添加ejs热更新代码`);
return resource + `
if(process.env.NODE_ENV === 'development') {
require('raw-loader!@views/${filename}.ejs')
}
if(module.hot) {
module.hot.accept();
module.hot.dispose(() => {
const axios = require('axios');
const href = window.location.href
axios.get(href).then(res => {
const template = res.data
document.body.innerHTML = template
}).catch(e => {
console.error(e)
})
})
}`
}
return resource;
}
同时修改webpack中的js文件的loader
{
test: /.js$/,
use: [
'babel-loader',
resolve(__dirname, 'auto-update-ejs-loader') // 添加增加热更新文件的loader
],
exclude: /node_modules/
}
结语
除去上述的功能,代码的完整配置里在生产环境下也包含了提取css,splitchunk等功能。可以直接下载代码运行起来~~