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

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

内容简介:最近接了一个公司官网的项目,需要 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:


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

查看所有标签

猜你喜欢:

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

The Black Box Society

The Black Box Society

Frank Pasquale / Harvard University Press / 2015-1-5 / USD 35.00

Every day, corporations are connecting the dots about our personal behavior—silently scrutinizing clues left behind by our work habits and Internet use. The data compiled and portraits created are inc......一起来看看 《The Black Box Society》 这本书的介绍吧!

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具