webpack搭建服务端项目

webpack搭建服务端项目

参考文章: https://juejin.im/post/5cb1aabdf265da037b6101d3

前言

最近有如下一些零零碎碎的小需求,总结起来,都是页面偏向于展示,与用户交互较少。因此,选择搭建了一个多页面的服务端项目。

  1. 低版本app用户展示的升级页面,
  2. 以及其他需要作为app中一些用来帮忙实现部分功能的中间页面

整个项目采用了webpack4 + express + ejs实现。

代码地址:https://github.com/chestnut647/serverSideRendering

webpack构建流程

由于是第一次脱离框架脚手架直接写webpack配置,花费了些时间。整个思路如下:

  1. 构思好项目的结构
  2. 先简单配置entry 和output配置
  3. 需要支持es6【模块化】,配置babel-loader
  4. 需要输出html文件并直接js的自动引入,引入html-loader loader以及html-webpack-plugin插件
  5. 不支持ejs语法中include语法,使用ejs-html-loader
  6. 项目没有热更新,引入webpack-dev-middleware和webpack-hot-middleware

开始构建

介绍构建过程中的一些重点,项目初始架构如下所示:

image

build内文件如下,config.json用于存放一些webpack使用的常量配置

image

配置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来实现。

  1. webpack-dev-middleware 用以实现文件变动自动编译
  2. 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模版文件的入口,增加如下代码。

  1. 在js中直接require模版文件,当ejs修改之后,webpack就会将其视为需要热更新的一部分
  2. 进行模块热替换,重新获取打包后文件的内容,替换到当前页面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等功能。可以直接下载代码运行起来~~

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,132评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,802评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,566评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,858评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,867评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,695评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,064评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,705评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,915评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,677评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,796评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,432评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,041评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,992评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,223评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,185评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,535评论 2 343

推荐阅读更多精彩内容

  • 在现在的前端开发中,前后端分离、模块化开发、版本控制、文件合并与压缩、mock数据等等一些原本后端的思想开始...
    Charlot阅读 5,429评论 1 32
  • 写在开头 先说说为什么要写这篇文章, 最初的原因是组里的小朋友们看了webpack文档后, 表情都是这样的: (摘...
    Lefter阅读 5,273评论 4 31
  • 构建一个小项目——FlyBird,学习webpack和react。(本文成文于2017/2/25) 从webpac...
    布蕾布蕾阅读 16,801评论 31 98
  • 我不是傻 只是懒得去计较 因为计较多了 快乐就少了 你的身边有没有人说你傻?什么事都为别人着想,受了伤、吃了亏还傻...
    清浅白芷阅读 1,946评论 2 19
  • 很多人都想养成阅读、健身等好习惯,也有很多人想戒掉抽烟、酗酒等坏习惯一样,但我们经常会半途而废。为何好习惯如...
    婧姐2019阅读 2,803评论 0 4