Webpack/Koa/Ejs/Sass实现官网

1,700 阅读6分钟

2020年4月1日,毕业将近两年的我完成了第一次职场跳槽,入职到现在所在的公司,上家是一家电商公司,所做项目就是一个供公司内部使用的ERP系统。作为一个小小的初级前端搬砖工人,我主要负责的工作就是根据高级工程师搭建好的大框架,在里面重复使用大佬写好的轮子实现页面,基本上没有机会去基础底层的框架使用。新公司是一家大数据业务公司,前端需要用到的技术会多很多,前后端都需要用到。持续学习,不断进步,共勉。

来到新公司所接到的第一项工作是对公司官网页面的重构,大体框架还是以前的大佬实现好的,用到的技术栈页确定了,但是对我来说都是新知识。最后用了两个星期左右的时间才终于做好并初步交付成功,藉此机会,记录一下在这个项目学习和使用到的新知识。

官网地址:sugo.io/

主要技术

  • 打包工具:Webpack
  • 服务器:koa
  • 前端:Ejs、Sass、Bootstrap、jQuery、jQuery.tmpl

Webpack

我对 webpack 的了解只属于入门级别,只是对照入门教程简单使用了一下。

 - 入门 Webpack,看这篇就够了 

下面记录一下使用到的 plugin 。

plugins: [
    new ExtractTextPlugin({
        filename: 'css/[name].css',
        allChunks: true
    }),
    new webpack.optimize.UglifyJsPlugin(),
    new webpack.LoaderOptionsPlugin({
        postcss: [autoprefixer]
    })
]

1. extract-text-webpack-plugin

这个插件可以将匹配到的 .css / .sass 文件分别打包到分离的 css 文件,这样就不会内联到 JS Bundle 。JS Bundle 和 CSS Bundle 是平行加载的,如果你的 css 文件很大,这样可以达到提速的效果。因为这个官网是单页面应用,每一个页面对应一个路径,因此使用这个插件的配置后可以把对应的 .sass 打包到 对应文件名的 .css 文件。filename: 'css/[name].css'

学习文章:extract-text-webpack-plugin 、webpack学习笔记之六,extract-text-webpack-plugin

2. UglifyJsPlugin

uglifyJsPlugin 是用来对 JS 文件进行压缩的,压缩 JS 文件的大小,以加快 load 速度。但是有个问题就是这个插件会拖慢 webpack 的编译速度,因此建议在开发的时候不要用它,在部署到生产环境的时候再打开。具体方法就是在 webpack.config.js 文件里面加个是否是生产环境的判断就行了。

if (process.env.NODE_ENV === 'production') {
  exportObj.plugins.push(new webpack.optimize.UglifyJsPlugin())}

3. LoaderOptionsPlugin

用来加载 loader 的一些配置选项。比如一个网站需要适配不同的浏览器,css 样式上需要加不同的前端来区别。但是我们不可能每次都手动去写,这样麻烦而且增加了工作量。这时候就可以使用 postcss loader 来解决,然后在 LoaderOptionsPlugin 这个插件这里进行 postcss loader 的配置。具体步骤可以参考:Webpack使用less以及postcss-loader

Koa

与 Express 一样,Koa 也是基于 node.js 的 web 开发框架。使用 async 函数丢弃函数回调,增强错误处理能力,相对于其它开发框架来说,Koa 更简洁,不捆绑任何中间件,而是提供一套功能强大的方法。具体参考官方:Koa 官方文档 。下面是这个官网项目服务器端需要用到的一些中间件。

1. koa-mount

将其它应用程序作为中间件挂载到当前 koa ,传递给 mount 函数的路径会暂时从 URL 中剥离出来,直到堆栈释放。

const Koa = require('koa')
const mount = require('koa-amount')
// a
const a = new Koa()
a.use(async (ctx, next) => {
    await next()
    ctx.body = 'Hello'
})
// b
const b = new Koa()
b.use(async (ctx, next) => {
    await next()
    ctx.body = 'World'
})
// app
const app = new Koa()
app.use(mount('/hello', a))
app.use(mount('/world', b))

app.listen(3000)

学习链接:koa 帮助文档

2. koa-router

koa-router 是用于实现路由功能的一个中间件,一个比较有趣的说法就是,koa 就像一个菜市场,它本身只需要维护菜市场的正常运行即可,而一系列额外的中间件就像菜市场中的菜摊,与我们打交道的大多都是这些中间件菜摊,而 koa-router 就是其中生意巨好的一个。

const Koa = require('koa')
const app = new Koa()
const router = require('koa-router')()
router.get('/', async ctx => {
    ctx.body = 'hi there'
})
app.use(router.routes())
    .use(router.allowMethods())

学习链接:

官方文档

koa-router是什么?详解koa-router的大致结构与运行流程

3. koa-static

koa-static 用来在服务器配置静态资源目录,方便读取文件。

const send = require('koa-static')
const staticOption = () => ({
    maxAge: 1000 * 60 * 60 * 24 * 365,
    hidden: true,  gzip: true
})
app.use(send(path.resolve(__dirname, './public'), staticOption()))

比如我们在工程的 public/img 中保存项目需要用到的图片资源,在代码中配置好静态资源路径后 app.use(send(path.resolve(__dirname, './public'))我们就可以在项目中很方便地读取到静态资源文件夹下的文件了,注意,在输入读取路径时不需要再输入 public ,直接 img/xxx.png 就可以了。

4. koa-ejs

const render = require('koa-ejs')
const path = require('path')
const Koa = require('koa')
const app = new Koa()
render(app, {
    root: path.resolve(__dirname, './view'),
    layout: false,
    viewExt: 'ejs',
    cache: true,
    debug: process.env.NODE_EVN === "development"
})

用于 ejs 模板的使用,具体参数可参考:官方文档

5. koa-etag 和 koa-conditional-get

这两个中间件互相搭配使用,实现基于 etag 的缓存功能。etag 判断内容是否发生改变,若内容未发生改变,返回304。否则返回带有内容的回复。

const etag = require('koa-etag')const conditional = require('koa-conditional-get')
app.use(conditional())app.use(etag())

学习链接:

www.npmjs.com/package/koa…

www.npmjs.com/package/koa…

6. koa-bodyparser

koa-bodyparser 是可以把 post 请求的请求参数解析到 ctx.request.body 中的中间件。

const parser = require('koa-bodyparser')
app.use(parser())

Ejs

ejs 就是一个 JavaScript 的嵌入式模板,其中的 ‘e’ 就是 effective (效率)的意思,通过特定的操作符可以用数据动态生成 html ,用起来还是比较有趣和方便的。具体的使用在官方文档中写得非常详细,这里就不做赘述了。说一下我在使用期间遇到的一个小难点,我在服务器端定义好了静态数据,通过全局变量传递到 ejs 模板页面后,使用 <% - datas.caseDatas %> (datas) 就是我定义的对象,但是在 ejs 模板页面中,我想写一点 JavaScript 脚本处理一些交互,突然发现不知道怎么去拿服务器端传递下来的数据了,后面经过尝试发现,在

const caseDatas = JSON.parse('<%- JSON.stringify(datas.caseDatas) %>');

Sass

Sass 是非常强大的 css 拓展语言,以前我只听说过学习过基础,但是没有在实际项目中使用过。通过这个项目的锻炼,我总结了 sass 中几个比较常用的功能。

先贴一下官方文档:www.sass.hk/docs/

1. 嵌套规则

sass 可以将一套 css 样式嵌套到另一套 css 样式中,里层的选择器会将外层的选择器作为父选择器,使用 & 符号可以得到父选择器。

@function grid-width($container, $num, $gutter) {
    @return floor(($container - $gutter * ($num - 1)) / $num);
}

.right {
    $gutter: 10px;
    $img-width: grid-width(570px, 3, $gutter) - 10;
    $img-height-row: $img-width * 9 / 14;
    $img-height-col: $img-width * 16 / 12;
    width: 50%;
    height: 500px;
    overflow-y: scroll;

    &:hover {
        @extend %box-shadow;
    }

2. 常量定义与四则运算

如上面贴的代码所示,sass 可以在选择器定义局部常量,甚至可以在 sass 文件开头定义全局常量。还可以使用+-*/实现四则运算,还可以将常用的计算提取成函数,通过参数传递需要计算的值,返回计算结果。然后直接通过函数名调用,跟写 JS 一毛一样哈哈。

3. 混合指令

混合指令(Mixin)用于定义可重复使用的样式,避免了使用无语意的 class,比如 .float-left。混合指令可以包含所有的 CSS 规则,绝大部分 Sass 规则,甚至通过参数功能引入变量,输出多样化的样式。

@mixin silly-links {
  a {
    color: blue;
    background-color: red;
  }
}
@include silly-links;

sass 有很多有趣的功能,很高效并且使用起来非常简单。一般看两篇官方文档就可以慢慢去写了,边用边学才能印象深刻。

jQuery.tmpl

jquery.tmpl 跟 ejs 一样,都是一个 JS 嵌入式模板,但是为什么我既然用了 ejs ,又要使用 jQuery.tmpl 呢?在实现案例页面的时候,需求需要做到分页后动态刷新页面,而不是整个页面重新加载,使用 ejs ,我只能通过 url 传参重新加载页面后才能拿到分页的参数进行数据过滤,实现分页。但这不是我想要的效果,后面我发现 jQuery.tmpl 这个模板更加方便,它可以动态修改 DOM 结构,通过 JSON 的格式传递和绑定数据,体积小速度快。

常用的标签就只有几个:${}、{{each}}、{{if}}、{{else}}

<script id="paginationTmpl" type="text/x-jquery-tmpl">
    {{each(i, it) arr}}
        <a href="javascript:;" class="${$data.pageNum==it ? "active" : ''}">${it}</a>
    {{/each}}
</script>
// data:拼凑好的需要在模板里面使用到的数据
$('#paginationTmpl').tmpl(data).appendTo('#paginationDiv');

pm2

pm2是一个进程管理工具,可以用它来管理你的node进程,并查看node进程的状态,当然也支持性能监控,进程守护,负载均衡等功能。

在项目开发接近尾声的时候,我们可能需要把开发成果展示给测试或者 UI 看,但是又不想每次都去部署测试环境,太麻烦了。于是我用 pm2 发布工程到本地 IP ,让其他人访问我的 IP 。在验收阶段,UI 提出一些优化,我这边快速解决并且重新打包发布一下就可以让 UI 快速看到结果了,这大大提高了效率。 

全局安装:npm i -g pm2

启动进程:NODE_ENV=production pm2 start app.js

重新打包:npm run build

重新发布:pm2 restart all

总结

上面只是对这次项目中使用的只是做一个比较粗略地概况,希望工作之余可以抽时间对这些知识进行深入的研究,理解底层。