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等功能。可以直接下载代码运行起来~~

你可能感兴趣的:(webpack搭建服务端项目)