koa+react+webpack搭建的新框架

858 阅读7分钟

目前所负责的项目要启动重构了。虽然之前也写过node和webpack的demo,但对整体的框架认知还不够全面,这次在搭建过程中,了解了每一个中间件所发挥的作用。收益很多。并把一些重要的知识点做了总结。

整体思路

多路由多页面应用设计

目录结构

.
├── client
│   ├── libs
│   │   └── qn.js
│   ├── npm-scripts
│   │   ├── _prompt.js
│   │   └── dev.js
│   ├── package.json
│   ├── pages
│   │   └── 2019-spokesman
│   ├── webpack
│   │   └── getPlugins.js
│   ├── webpack.dev.js
│   └── webpack.pro.js
├── dist
│   ├── index.js
│   └── index.js.map
├── nodemon.json
├── package.json
├── public
│   ├── dist
│   │   ├── 2019-spokesman-main-9f9d20d4.js
│   │   ├── 2019-spokesman-main-9f9d20d4.js.map
│   │   ├── 2019-spokesman-vendor-8702176e.js
│   │   └── 2019-spokesman-vendor-8702176e.js.map
│   └── favicon.ico
├── server
│   ├── config
│   │   ├── env
│   │   ├── index.ts
│   │   └── qconf.ts
│   ├── index.ts
│   ├── middlewares
│   │   ├── pageNotFound.ts
│   │   ├── registerRouter.ts
│   │   └── wechat.ts
│   ├── routers
│   │   └── demo.ts
│   ├── types
│   │   ├── config.d.ts
│   │   └── wx.d.ts
│   └── wx
│       ├── index.ts
│       └── libs
├── tsconfig.json
└── views
    └── 2019-spokesman.hbs

server 文件下的入口文件 -- index.ts

  • koa 2+

  • koa-base-logger 日志中间件

  • koa-body post 请求中间件

    • post请求数据默认存放在ctx.request.body
        app.use(koaBody({
            // 是否支持 multipart-formdate 的表单, 默认false不支持 
            multipart: true,
        }))
    
  • koa-views 处理模版中间件

        app.use(Views(viewsPath, {
            extension: 'hbs',
            // 将文件扩展名映射到引擎
            map: { hbs: 'handlebars' },
        }))
    
  • koa-helmet http请求头,安全性相关设置

  • koa-static 静态文件中间件

        const serve = require('koa-static')
        app.use(serve(path.resolve(__dirname, 'xxx')))
        // xxxwenji
        require('koa-static')(root, org)
        // 官方解释:root directory string. nothing above this root directory can be served
        * 安全漏洞:根据静态资源文件地址访问到整个服务器目录 做如下处理。
        // 对于method:为get、protocol:为·http协议的请求重定向,禁止恶意的带':'访问
         // 问题链接 https://App.blued.cn:443/file%3a///etc/passwd
        if (/\w*:/i.test(decodeURIComponent(ctx.path))) {
            ctx.redirect('/common/error/404')
            return false
        }
    
  • koa-router 路由中间件

  • koa-compose

  • glob 匹配文件 glob.sync('*.js') 返回当前文件夹下 js文件数组集合

  • 对访问地址状态的处理 处理ctx.status 状态为404,403时,重定向页面地址

  • 其他配置的中间件

前端页面

webpack 配置

  • css 与 js 分离

    import ExtractTextPlugin from 'extract-text-webpack-plugin'
    
    • 如果你的样式文件大小较大,这会做更快提前加载,因为 CSS bundle 会跟 JS bundle 并行加载。
  • HappyPack

    运行在Node.js 上的webpack是单线程模型的。happypack 把任务分解给多个子进程去并发执行,子进程处理完后再把结果发给主进程

    • 参数
        {
            threadPool: // 代表共享进程池,即多个HappyPack实例都使用同一个共享进程池中的子进程去处理任务
            verbose // 是否允许happypack 输出日志,默认是true
            threads // 代表开启几个子进程去处理这一类型的文件,默认是3个,类型必须是整型。
            
        }
    
    • 每通过 new HappyPack() 实例化一个 HappyPack 其实就是告诉 HappyPack 核心调度器如何通过一系列 Loader 去转换一类文件,并且可以指定如何给这类转换操作分配子进程。
  • loaders loaders: ['babel-loader?cacheDirectory'] 你也可以通过使用 cacheDirectory 选项,将 babel-loader 提速至少两倍。 这会将转译的结果缓存到文件系统中。

plugins

  • webpack.DefindPlugin

    • 因为这个插件直接执行文本替换,给定的值必须包含字符串本身内的实际引号。通常,有两种方式来达到这个效果,使用 "production", 或者使用 JSON.stringify('production')
  • html-webpack-plugin 动态注入css js 到html 模版

  • hash chunkhash contenthash

    • chunkhash 根据模块内容计算出的hash值
    • contenthash代表的是文本文件内容的hash值

动态生成script 命令

  • inquirer 交互式命令工具
        inquirer.prompt(questions).then(({...}) => {
            
        })
        传递给prompt方法的参数为一个question问题数组
    
        question = [
        {
            type: String, // 表示提问的类型,下文会单独解释
            name: String, // 在最后获取到的answers回答对象中,作为当前这个问题的键
            message: String|Function, // 打印出来的问题标题,如果为函数的话
            default: String|Number|Array|Function, // 用户不输入回答时,问题的默认值。或者使用函数来return一个默认值。假如为函数时,函数第一个参数为当前问题的输入答案。
            choices: Array|Function, // 给出一个选择的列表,假如是一个函数的话,第一个参数为当前问题的输入答案。为数组时,数组的每个元素可以为基本类型中的值。
            validate: Function, // 接受用户输入,并且当值合法时,函数返回true。当函数返回false时,一个默认的错误信息会被提供给用户。
            filter: Function, // 接受用户输入并且将值转化后返回填充入最后的answers对象内。
            when: Function|Boolean, // 接受当前用户输入的answers对象,并且通过返回true或者false来决定是否当前的问题应该去问。也可以是简单类型的值。
            pageSize: Number, // 改变渲染list,rawlist,expand或者checkbox时的行数的长度。
        }]
    
  • ora 角标

require 与 import 的区别

commonJs 规范 以modules.exports 导出接口 以require 引入模块

modules.exports 只是node 私有的一个全局变量属性,跟标准半毛钱关系都没有

  • export 导出模块接口
    // 错误演示
    export 1; // 绝对不可以
    var a = 100;
    export a;

export 在导出接口的时候,必须与模块内部的变量具有一一对应的关系。直接导出1没有任何意义,也不可能在import的时候有一个变量与之对应。export 虽然看上去成立,但是a的值是一个数字,根本无法完成解构,因此必须写成export {a} 的形式,即使a被赋值为一个function ,也是不允许的。 而且,大部分风格都建议,模块中最好在末尾用一个export导出所有的接口。例如:

    export {fun as default, a, b, c}
  • import导入模块 import 必须放在文件的最开始,且前面不允许有其他逻辑代码 import 的使用和export一样,也挺复杂,可以在这里大致了解。
    import $ from 'jquery';
    import * as _ from '_';
    import {a,b,c} from './a';
    import {default as alias, a as a_a, b, c} from './a';
import 后面跟上花括号的形式是最基本的用法,花括号里面的变量与export后面的变量一一对应。
  • as关键字

        var a = function() {};
        export {a as fun};
        
        // b.js
        import {fun as a} from './a';
        a();
    

    export的时候,对外提供的接口是fun,它是a.js 内部a这个函数的别名,但是在模块外面,认不到a ,只能认到fun import 中的as就很简单,就是你在使用模块里面的方法的时候,给这个方法取个别名。

  • default 关键字 别名的语法糖 import 的时候,可以省去花括号,简单的说,如果import的时候,你发现某个变量没有花括号括起来(没有*号),那么你的脑海中应该把它还原成有花括号的as语法。

        import a,{each,map} from 'jquery'
    
    • import 后面第一个a是 {default as a}的语法糖
  • *符号

    *就是代表所有,只用在import 中,

        import * as _ from '_'
    

    在意义上和import _ from '_'是不同的,它表示的是把'_'模块中的所有接口挂载到 _ 这个对象上,所以可以用_.each 调用某个接口

    另外还可以通过*号直接继承某一个模块的接口

        export * from '_'
        // 等效于
        import * as all from '_'
        export all;
    

    *符号尽可能少用,它实际上是使用所有export的接口,但是很有可能你的当前模块并不会用到所有接口,所以最好的建议是使用花括号,用一个加一个

  • 该用require还是import

    require的使用非常简单,它相当于module.exports 的传送门,module.exports后面的内容是什么,require的结果就是什么,对象、数字、字符串、函数......再把require 的结果赋值给某个变量,相当于把require和module.exports 进行平行空间的位置重叠。 其实还是没太整明白什么时候用import 什么时候用require