webpack4 + ejs + express 带你撸一个多页应用项目架构

栏目: Node.js · 发布时间: 5年前

内容简介:最近接了一个公司官网的项目,需要 SEO 友好,所以不能使用前端框架,前端框架自带的脚手架工具自然也帮不上啥忙。只好自己使用以下我会将重要的细节标红,给需要的朋友参考。在动手开发之前,我们需要先明确这个项目的定位——

最近接了一个公司官网的项目,需要 SEO 友好,所以不能使用前端框架,前端框架自带的脚手架 工具 自然也帮不上啥忙。只好自己使用 webpack4 + ejs + express ,从头搭建一个多页应用的项目架构。搭建过程中,遇到许多坑,然而网上的相关参考也是非常少,所以写个博客记录一下搭建过程以及注意事项。

以下我会将重要的细节标红,给需要的朋友参考。

明确需求

在动手开发之前,我们需要先明确这个项目的定位—— 公司官网 ,一般来说,官网不会涉及大量的数据交互,比较偏向于数据展示。所以不用前端框架, jquery 即可满足需求。但是考虑到 SEO 所以需要用到服务端渲染,就要使用模板语言( ejs ),配合 node 来完成。

根据以上信息,我们就可以确定打包脚本的基本功能,先来简单列个清单:

  1. 需要 webpack 来打包多页应用,且不需要每次新增一个视图文件都添加一个 HTMLWebpackPlugin 和重启 server ,能做到 webpack 配置和文件名解耦,尽量的自动化。
  2. 需要使用 ejs 模板语言编写,能够插入变量和外部 includes 文件,最后运行 build 命令的时候能将通用模板文件( <meta>/<title>/<header>/<footer> 等)自动插入每个视图文件对应位置。
  3. 需要用到服务端渲染,所以开发环境要脱离 webpack 集成的 webpack-dev-server ,能使用自己编写的 node 代码启动服务。
  4. 拥有完善的 overlay 功能,可以像 webpack-dev-server 那样集成漂亮的 overlay 屏幕报错。
  5. 能监听文件变化,自动打包和重启服务,最好能做到热更新

开始构建

先建立一个空项目,由于需要自己编写服务端代码,所以我们需要多建一个 /server 文件夹,用来存放 express 的代码,搭建完成后,我们的项目结构看起来是这样。

webpack4 + ejs + express 带你撸一个多页应用项目架构

除此以外,我们需要初始化一些通用配置文件,包括:

.gitignore
.editorConfig
.eslintrc.js
README.md
package.json

大的框架出来以后,开始编写工程代码。

打包脚本

首先是编写打包脚本,在 /build 文件夹里新建几个文件

webpack.base.config.js
webpack.dev.config.js
webpack.prod.config.js
config.json

一般来说, webpack.base.config 文件里,放一些开发生产环境通用的配置,例如 outputentry 以及一些 loader 例如编译ES6语法的 babel-loader 、打包文件的 file-loader 等。常用的 loader 的使用方式我们可以查看文档webpack loaders,

需要注意的是,这边有个非常重要的 loader ———— ejs-html-loader

一般来说,我们使用 html-loader 来对 .html 结尾的视图文件做处理,然后扔给 html-webpack-plugin 生成对应的文件,但是 html-loader 无法处理 ejs 模板语法中的 <% include ... %> 语法,会报错。在多页应用里,这个 include 的功能是必须的,不然每个视图文件里都要手动去写一份 header/footer 是什么感觉。。。所以我们需要再多配置一份 ejs-html-loader:

webpack4 + ejs + express 带你撸一个多页应用项目架构

第一个坑绕过之后,第二个:

entry 入口要怎么写?

记得之前公司的一个老项目,五十几个页面,五十几个 entrynew HTMLwebpackPlugin() 一个文件展开来可以绕地球一圈。。。这边为了避免这种惨状,写一个方法,返回一个 entry 数组。

可以使用glob 来处理这些文件,获取文件名,当然同样也可以使用原生 node 来实现。只要保证 JavaScript 文件名和视图文件名相同即可,比如,首页的视图文件名是 home.ejs ,那么对应的脚本文件名就要用同样的名字 home.js 来命名,webpack 打包的时候会找到脚本文件入口,通过映射关系生成对应视图文件:

webpack4 + ejs + express 带你撸一个多页应用项目架构

HTMLWebpackPlugin 也同理:

webpack4 + ejs + express 带你撸一个多页应用项目架构

编写好 webpack.base.config.js 文件,根据自己项目需求编写好 webpack.dev.config.jswebpack.prod.config.js ,使用webpack-merge 将基础配置和对应环境下的配置合并。

webpack 其他的一些细节配置大家可以参考webpack 中文网址

服务端

打包脚本编写完成,我们开始编写服务,我们使用 express 来搭建服务。 (由于是工程架构演示,所以这个服务暂不涉及任何的数据库的增删改查,只是包含基本的路由跳转)

server 简单的结构如下:

webpack4 + ejs + express 带你撸一个多页应用项目架构

服务端启动文件

bin/server.js 启动文件,作为服务的入口,需要同时启动本地服务和 webpack 的开发时编译。一般项目 webpack-dev-server 是写在 package.json 里的,当你运行 npm run dev 的时候,就在使用 webpack-dev-server 启动开发服务,这个 webpack-dev-server 功能十分强大,不仅能一键启动本地服务,还可以监听模块,实时编译。这边我们使用 express + webpack-dev-middleware 也可以达到同样的功能。

webpack-dev-middleware 可以理解为一个抽离出来的 webpack-dev-server,只是没有启动本地服务的功能,以及使用方式上略有改变。它相比于 webpack-dev-server 的灵活性在于,它以一个中间件的形式存在,允许开发者编写自己的服务来使用它。

其实 webpack-dev-server 的内部实现机制也是借助于 webpack-dev-middleware 和 express 有兴趣的朋友可以去看一下。

以下是服务入口文件的部分代码

webpack4 + ejs + express 带你撸一个多页应用项目架构

服务端路由

路由的跳转方式,属于整个工程中非常重要的一步。不知道阅读文章的朋友有没有疑问,本地的视图文件是 .ejs 后缀结尾的文件,浏览器只能识别 .html 后缀文件,这块视图数据的渲染是怎么做的? webpack-dev-middleware 打包出来的资源都是存在内存中的,存储在内存中的资源文件,服务端要怎么获取?

先来看具体的路由代码,此处以首页路由作为演示

webpack4 + ejs + express 带你撸一个多页应用项目架构

再来看看这个 getTemplate 做了咩

webpack4 + ejs + express 带你撸一个多页应用项目架构

从上面代码可以看到,路由中的做的非常重要的事情,就是直接用对应视图的 ejs 文件名,去请求自身服务,从而获取到存在 webpack 缓存中的资源和数据。

通过这种方式拿到模板字符串后,ejs 引擎会用数据渲染对应变量,最终以 html 字符串的形式返回到浏览器进行渲染。

本地服务会以一个 publicPath 路径前缀来标记静态资源请求,如果服务接受到的请求是带有 publicPath 前缀,就会被 `/bin/server.js` 中的静态资源中间件拦截到,映射到对应资源目录,返回静态资源,而这个 publicPath 就是 webpack 配置中的

output.publicPath

关于 webpack 的打包时缓存,我之前翻了很多地方都没有找到很好的文档和操作工具,这边给大家推荐两个链接

  1. Webpack Custom File Systems (webpack 自定义文件系统官方说明)
  2. memory-fs (获取 webpack 编译到内存中的数据)

客户端

完成了服务端渲染、webpack 构建配置后,算是搞定了 80% 的工作量,还有一些小细节需要注意,不然服务启动起来还是会报错。

webpack 编译时的坑

这个坑就埋在客户端的视图文件里,先来看看坑是什么:当我们使用 ejs 语法( <%= title %> )这种语法的时候,webpack 编译就会报错,说是 title is undefined

要解决这个问题,需要首先明白 webpack 编译时的运行机制,它做了什么。我们知道,webpack 内部模板机制就是基于的 ejs,所以在我们服务端渲染之前,也就是 webpack 的编译阶段,已经执行过了一次 ejs.render 了,这个时候,在 webpack 的配置文件里,我们是没有传递过 title 这个变量的,所以编译会报错。那么要怎么写才能识别呢?答案就在ejs 的官方文档

webpack4 + ejs + express 带你撸一个多页应用项目架构

从官网的介绍上可以看出,当我们使用 <%% 打头的时候,会被转义成 <% 字符串,类似于 html 标签的转义,这样才能避免 webpack 中自带的 ejs 的错误识别,生成正确的 ejs 文件。所以以变量为例,在代码中我们需要这样写: <%%= title %>

这样,webpack 才能顺利编译完成,将 compiler 继续传递到 ejs-html-loader 这里

使用 html-loader 识别图片资源

如果了解 html-loader 的朋友就知道,在项目中,我们之所以能够在 html 中方便的写 <img src="../static/imgs/XXX.png"> 这种图片格式,还能被 webpack 正确识别,离不开 html-loader 里的配置

webpack4 + ejs + express 带你撸一个多页应用项目架构

但是在 ejs-html-loader 里,没有提供这种方便的功能,所以我们依旧要使用 html-loader 来对 html 中的图片引用做处理,这边需要注意 loader 的配置顺序

webpack4 + ejs + express 带你撸一个多页应用项目架构

配置热更新

接下来是配置热更新,使用 webpack-dev-middleware 时的热更新配置方式和 webpack-dev-server 略有不同,但是 webpack-dev-middleware 稍微简单一点。webpack 打包多页应用配置热更新,一共四步:

  1. entry 入口里多写一个 webpack-hot-middleware/client?reload=true 的入口文件
webpack4 + ejs + express 带你撸一个多页应用项目架构
  1. 在 webpack 的 plugins 里多写三个 plugin:
    plugins: [
    ...
    
    // OccurrenceOrderPlugin is needed for webpack 1.x only
    new Webpack.optimize.OccurrenceOrderPlugin(),
    new Webpack.HotModuleReplacementPlugin(),
    // Use NoErrorsPlugin for webpack 1.x
    new Webpack.NoEmitOnErrorsPlugin()
    
    ...
    ]
    复制代码
  2. bin/server.js 服务入口中引入 webpack-hot-middleware , 并将 webpack-dev-server 打包完成的 compilerwebpack-hot-middleware 包装起来:
    let compiler = webpack(webpackConfig)
    
    // 用 webpack-dev-middleware 启动 webpack 编译
    app.use(webpackDevMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        overlay: true,
        hot: true
    }))
    
    // 使用 webpack-hot-middleware 支持热更新
    app.use(webpackHotMiddleware(compiler, {
        publicPath: webpackConfig.output.publicPath,
        reload: true,
        noInfo: true
    }))
    复制代码
  3. 在视图对应的 js 文件里加一段代码:
    if (module.hot) {
        module.hot.accept()
    }
    复制代码

关于 webpack-hot-middleware 的更多配置细节,请看文档

这边需要注意的是:光是这么写的话,webpack hot module 只能支持 JS 部分的修改,如果需要支持样式文件( css / less / sass ... )的 hot reload ,就不能使用 extract-text-webpack-plugin 将样式文件剥离出去,否则无法监听修改、实时刷新。而且,webpack hot module 默认是不支持 html 的热替换的

这是我翻到的相关 issues 截图,如果是懒癌患者,实在是怕按个 f5 刷新,那么可以自己找下解决的办法。

webpack4 + ejs + express 带你撸一个多页应用项目架构

webpack-hot-middleware 默认继承了 overlay ,所以当热更新配置完成以后, overlay 报错功能也能正常使用了

webpack4 + ejs + express 带你撸一个多页应用项目架构

package.json 启动脚本

最后来看一下 package.json 里的启动脚本,这边没啥难度,就直接上代码了

webpack4 + ejs + express 带你撸一个多页应用项目架构

当客户端代码变动时 webpack 会自动帮我们编译重启,但是服务端的代码变动却不会实时刷新,这时需要用到 nodemon ,设置好监听目录以后,服务端的任何代码修改就能被 nodemon 监听,服务自动重启,非常方便。

这边也有一个小细节需要注意,nodemon --watch 最好指定监听服务端文件夹,因为毕竟只有服务端的代码修改才需要重启服务,不然默认监听整个根目录,写个样式都能重启服务,简直要把人烦死。

总结

项目整体搭完后再回头看,还是有不少需要注意和值得学习的地方。虽然踩了不少坑,但也对其中的一些原理有了更深入的了解。

得益于前端脚手架工具,让我们能在大部分项目中一键生成项目的基础配置,免去了很多工程搭建的烦恼,但这种方便在造福了开发者的同时,却也弱化了前端工程师的工程架构能力。现实中总有一些脚手架工具没办法的触及到的业务场景,这时就需要开发者主动寻求解决方案,甚至自己动手构建工程,以获得开发的最佳灵活性。

完整项目地址可以查看我的 GitHub ,喜欢的话给个 Star:star:️ ,多谢多谢~:smiley::smiley:


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Twisted Network Programming Essentials

Twisted Network Programming Essentials

Abe Fettig / O'Reilly Media, Inc. / 2005-10-20 / USD 29.95

Developing With Python's Event-driven Framework一起来看看 《Twisted Network Programming Essentials》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

XML、JSON 在线转换
XML、JSON 在线转换

在线XML、JSON转换工具

HEX CMYK 转换工具
HEX CMYK 转换工具

HEX CMYK 互转工具