『源代码』
注释:以下内容只是『koa2-webpack-boilerplate』的说明文档,算不得一篇文章,纯粹个人的随笔记录。
Table of Contents
- Features
- Requirements
- Installation
- Running the Project
- Project Structure
- Routing
- Webpack
- Eslint
- Pre-commit
- Base Configuration
- In development Mode
- In production Mode
- Mount
- 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:dev |
Serves your app in development mode |
star:prod |
Serves your app in production mode |
build |
Builds 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
上挂载link
和script
属性,用于在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,它可以有效的提示你对应的代码是否符合约定规则。
pre-commit
pre-commit
是一个 git
的勾子,它可以确保你在提交代码前需要通过你预设的相关约定;在脚手架中主要用来确保在你提交代码之前必须先通过所有的 Eslint
检查,否则不能提交。
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 只能用相对路径
- 可以代理整个应用,也可以只代理某个路由
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。