Skip to content

[Blog]基于ES6 的模块化开发 #2

@alex2wong

Description

@alex2wong

在没有框架的约束下,我们开发项目可能都是基于过程的,想到哪里就添加一个函数。这在项目开发的初期可能是很快的,特别是对于前端项目。但在后期修改需求的时候就发现 项目文件存在 功能不明确,职责混乱的情况,假如有Vue.js 或者Angular.js 等框架约束,这种情况会相对好些。本文记录下基于ES6 实践模块化开发的过程,本文所用到的代码在github项目上,欢迎各位大神指点。

Angular.js 架构图

这些MVC框架基本都着眼于以数据模型为中心,打造数据驱动的模块化前端应用。框架可能层出不穷,学也学不完,但基本的思想是不变的。以 Angular.js 的架构 为例,Component(组件) 和 Template(HTML模板) 分别代表了Web App的数据 和 视图两大部分,数据的存储、更新过程都是在我们定义的组件中,组件中包含的数据模型更新都会通过数据绑定引起视图的更新。

而用户对用户界面的操作,可以通过事先定义的各种Directive(指令),反馈到数据模型中。比如 ngModel 这样的指令就可用于绑定视图对数据模型的更新。基于Angular这样的框架开发过程中,基本就是不断写组件,写模板,写指令的过程。那么扯远了,Angular 的模块化系统和ES6 的还是有很大差异的。说了半天,框架毕竟是别人团队开发的,你大可去用,从ES6 这样的本源出发去学习实践更加以不变应万变。

ES6转码打包

由于大部分浏览器还没有支持ES6 模块,所以可采用Babel 转
来把我们的代码转化为es5.

然后用 Webpack 打包所有js文件 为一个bundle ,也可以采用SystemJS的依赖管理方案,实现浏览器端的模块加载

由于之前在Angular的 实践过程中采用的是 SystemJS,所以这次把两种方法都讨论演示下。需要说明的是,这两种浏览器端加载es6模块的方法都需要Babel的支持,根据具体情况可选用 Webpack 或SystemJS。

模块编写过程

比如我们现在有 drone 和 bullet 两个类,drone 可以通过fire() 方法创建bullet 实例,并且通过一个全局的 RenderBullet 方法计算bullet 轨迹。

就这么简单的需求,因为drone 和bullet 在我们的游戏应用中是 基础类,所以单独写成模块。常数变量至于const.js 中。

// drone.js
import Const from './const';
import Bullet from './bullet';

/**
 * Drone class with control method.
 */
export default class Drone {
    constructor(opts) {
        this.id;
        this.speed = opts.speed ? opts.speed: 0.01;
        this.direction = opts.direction ? opts.direction: 0;
        this.name = opts.name ? opts.name: this.randomName();
        this.life = Const.DroneParam.LIFE;
        this.bullets = [];
        this.firing = false;
        this.point = {
            type: 'Point',
            coordinates: [121, 31]
        }
        this.bulletNum = 2;
    }
    // .... 省略飞控代码。。  
  
    fire () {
        // if not firing, start firing for specific duration.
        if (!this.firing) {
            for (let i = 0; i < this.bulletNum; i ++) {
                this.bullets.push(new Bullet(this));
            }
            this.firing = true;
            setTimeout(() => this.firing = false, Cost.DroneParam.FIRINGTIME);
        }
    }
}

下面简单看下**bullet.js **的结构:

/**
 * Bullet based on Drone instance
 */
export default class Bullet {
    // opts should contain the Drone's direction and geometry
    constructor(opts) {
        this.id;
        this.direciton = opts.direction ? opts.direction: 0;
        this.spoint = {
            type: 'Point',
            coordinates: [0, 0]
        };
        // DeepCopy the drone coords to bullet.
        this.spoint.coordinates[0] = opts.point.coordinates[0];
        this.spoint.coordinates[1] = opts.point.coordinates[1];
    }
}

常量模块,包含静态属性,无需实例化直接调用:

export default class Const {
}

// Static Props outside of class definition
Const.DroneParam = {
    MAXSPEED: 3.999,
    FIRINGTIME: 800,
    LIFE: 10,
    // Firing range.. 0.2 rad in LngLat
    RANGE: 0.2 
};

至此,这就完成了几个基础模块的编写,注意: 现在drone.js, bullet.js const.js 这几个模块都在项目的src文件夹下,基于Babel 和 Webpack 转码打包需要如下过程:

Babel 和Webpack 安装配置

  • 首先npm 安装Babel 和 Webpack 库:

npm install babel-cli babel-core babel-loader webpack babel-preset-latest --save-dev

  • 第二,配置 .babelrc 。在项目根目录下创建 .babelrc,前面有一个点啊,别说没玩过linux。。配置文件都这熊样,内容跟官网一样。
{ "presets": ["latest"] }
  • 第三,配置 webpack.config.js如下.
module.exports = {
    entry: {
        index: [
            "./src/app.js"
            ]
    },
    output: {
        path: "./dist/",
        filename: "bundle.js",
        // app.js 中导出的模块都在Alex 这个Root 命名空间下
        library: 'Alex',
        libraryTarget: 'umd',
    },
    module: {
        loaders: [
        {
            // 用babel 作为 js loader,打包前转码为es5,没有中间文件
            test: /\.js$/,
            exclude: /node_modules/,
            loader: 'babel'
        }]
    }
};

说明一下,entry.index 指向的 ./src/app.js 是应用的入口文件,也就是说,drone, bullet 等等模块是写好了,但是还需要一个Root 模块来导出所有模块(API模式)或者启动应用(APP模式)。 当然上述两个模式是我胡诌的,但是经过实践确实证明这两种模式对应模块化的不同需求。

  • 假如你的 业务逻辑代码 都需要 采用es6 来模块化编写(往往是大型应用),那么你的app.js 应该包含业务代码(APP模式)
  • 假如你的 模块只是作为 API 供外部代码调用,比如 f3earth 这样的采用es6 编写的 API,那么你的app.js 应该只包含模块导出的过程(API模式)

比如我的app.js 长这样:

import Drone from './drone';
// 引入自行封装的Canvas,渲染游戏场景
import Canvas from './chart/canvas';

export {
        Drone,
        Canvas
} 

这里将所有子模块再次导出为一个根模块,对应webpack.config.js 中配置的名为 Alex 的根模块。在业务代码中通过 Alex.Drone, Alex.Canvas 来调用不同的类。
至此,就完成了打包前的工作,在根目录下 cmd中 通过webpack命令开始打包。完成之后,在 dist 目录下产生 bundle.js,那么这个文件包含了我们刚才所编写的所有模块,可供业务代码调用。

如果想详细了解 Babel,可以直接参考其官网栗子,各种babel 的用法(npm script,或者在webpack中作为loader)
如果想了解更多关于webpack,可以参考我看过比较简明易懂的 webpack 入门 这篇文章

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions