『源代码』

注释:以下内容只是『koa2-webpack-boilerplate』的说明文档,算不得一篇文章,纯粹个人的随笔记录。

Table of Contents

  1. Features
  2. Requirements
  3. Installation
  4. Running the Project
  5. Project Structure
  6. Live Development

    • HTML
    • Images
    • StyleSheets
    • JavaScript
  7. Routing
  8. Webpack
  9. Eslint
  10. Pre-commit
  11. Base Configuration
  12. In development Mode
  13. In production Mode
  14. Mount
  15. TODO

Features

This is a starter koa boilerplate app I've put together using the following technologies:

koa v2
webpack v3
ES2015+
Babel
SCSS
Hot reload
Eslint
pre-commit
✓ ...


Requirements

  • node ^6.0.0
  • npm ^5.0.0

Installation

基于 koa-boilerpate 开始一个新的项目

$ git clone git@github.com:chenbin92/koa2-webpack-boilerplate.git MyApp
$ cd MyApp
$ npm install        # Install project dependencies listed in package.json

Running the Project

安装项目依赖成功后,启动开发环境命令如下

// run the dev server http://localhost:3000
$ npm run dev:start

其他任务脚本

npm <script>Description
star:devServes your app in development mode
star:prodServes your app in production mode
buildBuilds the application
lint Lints the project for potential errors

Project Structure

一般项目结构可以按照文件类型功能类型或其他类型设计,每个团队每个项目都可能会有自己的项目结构。 koa2-webpack-boilerplate 奉行『约定优于配置』,按照一套统一的约定进行应用开发。

koa2-webpack-boilerpate
├── index.js                        # 用于自定义启动时的初始化工作(如配置babel-register)
├── src                             # 应用源代码
|   ├── assets                      # 静态资源
|   |   ├── images
|   |   ├── javascripts
|   |   └── stylesheets
|   ├── config                      # 用于编写配置文件
|   |   └── dictionary.js
|   | 
│   ├── controller                  # 用于解析用户的输入,处理后返回相应的结果
│   |   └── home.js
│   ├── service                     # 用于编写业务逻辑层
│   |   └── user.js
│   ├── middleware                  # 用于编写中间件
│   |   └── response_time.js
│   ├── public                      # 唯一对外开放的文件夹,存放静态文件和编译后的资源文件
│   |   └── favicon.ico
│   ├── view                        # 用于放置模板文件
│   |   └── home.html
│   └── router                     # 用于配置 URL 路由规则
│   |   └── index.js
├── build                           # 用于编写构建文件
|   ├── chalk.config.js
|   ├── project.config.js
|   └── webpack.prod.js
└── test                            # 用于单元测试

Live Development

HTML

我们使用 webpack 对 app/assets/* 目录下的文件进行动态编译打包至 app/public/* 目录下,通过 assetsMiddleware 中间件根据自动注入 bundle 文件。

大致思路是:

  • 在开发环境,直接使用webpack编译在内存的文件系统作为资源来源;
  • 在生产环境,先使用 assets-webpack-plugin 生成 assetsMap.json 文件,然后根据 assetName 映射;
  • ctx.state 上挂载 linkscript 属性,用于在 index.html 引用文件
ctx.state.script = (assetName) => {
     return `<script src='${getUrlByEnv(assetName)}'></script>`;
 };

 ctx.state.link = (assetName) => {
      return `<link rel='stylesheet' href='${getUrlByEnv(assetName)}'>`;
    };

完整代码

Images

应用图片默认的位置是 app/assets 文件夹中的 images,通过相关配置会监听并自动将图片自动映射到 app/public 目录下;

你可以这样引用图片:

// in HTML
<img src="images/egg_logo.svg" alt="logo">
// in SCSS
background-image: url("images/egg_logo.svg");

StyleSheets

推荐使用 SASS 进行样式编写;CSS 组织按照页面(page)和框架(framework)进行区分自定义的和第三方库的样式,通过 @import 导入到 application.css,目录结构形如:

├── stylesheets
|   ├── page
|   |    ├── homepage.csss
|   |    └── help.scss
|   ├── framework
|   |    ├── bootstrap.csss
|   |    └── button.scss
|   ├── application.scss

JavaScript

  • 应用按照功能模块化进行开发,主要有以下两种约定:

    • Global Module: 挂载在 window 对象
    • Namespace: 挂载在 app 对象上
  • 模块化的几种写法:

    • 方式一:挂载到 window 对象

      window.ModuleName = (function() {
        function Fn() {
          this.fn1();
        }
      
        Fn.prototype.fn1 = function() {}
      
        return Fn
      })();
    • 方式二:IIFE

      window.app = window.app || {};
      (function(app) {
        app.ModuleName = (function() {
          // your code...
        })();
      }).call(window, app);
      
      // or
      (function(global) {
        class ModuleName {
          // your code...
        }
      
        global.ModuleName = ModuleName
      })(window.appp || (window.app = {}));
    • 方式三:挂载到 app 对象上

      class ModuleName {
        // your code...
      }
      window.app = window.app || {};
      app.ModuleName = ModuleName;
    • 方式四: ES6 Class

      class ModuleName {
        constructor() {}
      
        fn() {}
      }
      export default ModuleName
    • 方式五: ...
  • 引用方式

在应用的 app/assets/javascripts/application.js 文件包含下面几行代码:

// import stylesheets
import '../stylesheets/application.scss';

// import page scripts
import './home';
import './about';

在应用的 app/assets/javascripts/vendors.js 文件包含下面几行代码:

// import Third-party libraries
import $ from 'jquery';
import _ from 'lodash';

在 JavaScript 文件中,主要分为以下几个部分按照从上到下的顺序处理的:

  • 引入应用的样式
  • 引入第三方库
  • 引入业务模块

Routing

简单演示约定

import Router from 'koa-router';
import home from '../controller/home';
import about from '../controller/about';

const appRoutes = () => {
  // TODO: 添加前缀会导致静态资源无法加载
  const router = new Router({
    prefix: '/test',
  });

  router
    .get('/', home)
    .get('/about', about);

  return router;
};

export default appRoutes;


Eslint

项目遵循 eslint-egg 规则;在开发模式下进行 eslint watching,它可以有效的提示你对应的代码是否符合约定规则。

eslint watching


pre-commit

pre-commit 是一个 git 的勾子,它可以确保你在提交代码前需要通过你预设的相关约定;在脚手架中主要用来确保在你提交代码之前必须先通过所有的 Eslint 检查,否则不能提交。

pre-commit


Base configuration

  • Copy images
  • Sass compile
  • Generate html
  • Expose global
  • Define plugin
  • Assets webpack plugin

In Development mode

  • HOT
  • File watching
  • Eslint watching
  • Pre commit

In Production mode

  • Uglify javascript
  • Extract stylesheets
  • Extract the common file
  • Eslint watch
  • Bundle file analyzer
  • Static file md5

Mount

需求:如何在一个域名下根据项目名称作为前缀,同时挂载多个 web 应用。

例如:

根域名:http://www.upchina.com/

A 应用:http://www.upchina.com/A

B 应用:http://www.upchina.com/B

C 应用:http://www.upchina.com/C

解决方案:通过 Mount 解决,其思想是把整个应用当作一个中间件,在 mount 内修改应用的 path,然后再次创建一个新的应用,将 mount 中间件传递

import Koa from 'koa'
import mount from 'mount'
import router from 'router'

// 传递给 mount 
const a = new Koa()
a.use(router().routes())

// app
const app = new Koa()
app.use(mount('/m', a))

app.listen(3001)

注意

  • 使用 Mount 只能用相对路径
  • 可以代理整个应用,也可以只代理某个路由

推荐阅读

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐