<kbd id="5sdj3"></kbd>
<th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>

    henriThe versatile Javascript framework

    聯(lián)合創(chuàng)作 · 2023-09-19 01:43

    henri - the versatile javascript framework

    npm version npm downloads Coverage Status Join slack

    henri is an easy to learn rails-like, server-side rendered (react & vue) with powerful ORMs

    How to use

    Install

      yarn global add henri
    
      # or
    
      npm install -g henri

    Create a new project

      henri new <folder name>

    The above command will create a directory structure similar to this:

    ├── app
    │   ├── controllers
    │   ├── helpers
    │   ├── models
    │   └── views
    │       ├── assets
    │       ├── components
    │       ├── pages
    │       ├── public
    │       │   ├── css
    │       │   ├── fonts
    │       │   ├── img
    │       │   ├── js
    │       │   └── patterns
    │       └── styles
    ├── config
    │   ├── default.json
    │   ├── production.json
    │   ├── routes.js
    │   └── webpack.js            <- Overload Next.js webpack settings
    ├── test
    │   ├── controllers
    │   ├── helpers
    │   ├── models
    │   └── views
    ├── package.json

    If you have a Ruby on Rails background, this might look familiar.

    One last step to start coding is:

      cd <folder name>
      henri server

    And you're good to go!

    Configuration

    The configuration is a json file located in the config directory.

    henri will try to load the file matching your NODE_ENV and will fallback to default.

    You can have a default.json, production.json, etc.

    {
      "stores": {
        "default": {
          "adapter": "mongoose",
          "url": "mongodb://user:[email protected]:10914/henri-test"
        },
        "dev": {
          "adapter": "disk"
        }
      },
      "secret": "25bb9ed0b0c44cc3549f1a09fc082a1aa3ec91fbd4ce9a090b",
      "renderer": "react"
    }

    Models

    You can easily add models under app/models.

    They will be autoloaded and available throughout your application (exposed globally).

    We use Mongoose for MongoDB, Sequelize for SQL adapters and Waterline for the disk adapter.

    You can use the command-line to generate models:

    # henri g model modelname name:string! age:number notes:string birthday:date!
    // app/models/User.js
    
    // Whenever you have a User model, it will be overloaded with the following:
    
    // email: string
    // password: string
    // beforeCreate: encrypts the password
    // beforeUpdate: encrypts the password
    
    module.exports = {
      store: 'dev', // see the demo configuration up there
      name: 'user_collection', // will use user_collection' instead of 'users'
      schema: {
        firstName: { type: 'string' },
        lastName: String,
        tasks: {},
      },
    };
    // app/models/Tasks.js
    
    module.exports = {
      store: 'default', // see the demo configuration up there
      schema: {
        name: { type: 'string', required: true },
        category: {
          type: 'string',
          validations: {
            isIn: ['urgent', 'high', 'medium', 'low'],
          },
          defaultsTo: 'low',
        },
      },
    };

    Disk

    The disk adapter is using Waterline to provide disk-based storage.

    This is not for production and you can easily port your models to other adapters.

      yarn add @usehenri/disk
    
      # or
    
      npm install @usehenri/disk --save

    MongoDB

    The MongoDB adapter is using Mongoose to provide a MongoDB ODM.

      yarn add @usehenri/mongoose
    
      # or
    
      npm install @usehenri/mongoose --save

    MySQL

    The MySQL adapter is using Sequelize to provide a MySQL ORM.

      yarn add @usehenri/mysql
    
      # or
    
      npm install @usehenri/mysql --save

    MSSQL

    The MSSQL adapter is using Sequelize to provide a MSSQL ORM.

      yarn add @usehenri/mssql
    
      # or
    
      npm install @usehenri/mssql --save

    PostgreSQL

    The PostgresQL adapter is also using Sequelize to provide a PostgresQL ORM.

      yarn add @usehenri/postgresql
    
      # or
    
      npm install @usehenri/postgresql --save

    GraphQL

    You can add a graphql key to your schema file and they will be automatically loaded, merged and available.

    Definition

    // app/models/Task.js
    
    const types = require('@usehenri/mongoose/types');
    
    module.exports = {
      schema: {
        description: { type: types.STRING, required: true },
        type: { type: types.ObjectId, ref: 'Type', required: true },
        location: { type: types.ObjectId, ref: 'Location', required: true },
        reference: { type: types.STRING, required: true },
        notes: { type: types.STRING },
        oos: { type: types.BOOLEAN, default: false },
      },
      options: {
        timestamps: true,
      },
      graphql: {
        types: `
          type Task {
            _id: ID!
            reference: String!
            description: String!
            location: Location
            type: Type
            notes: String!
            oos: Boolean
          }
          type Query {
            tasks: [Task]
            task(_id: ID!): Task
          }
        `,
        resolvers: {
          Query: {
            tasks: async () => {
              return Task.find()
                .populate('type location')
                .exec();
            },
            task: async (_, id) => await Task.findOne(id).populate('type'),
          },
        },
      },
    };

    Query

    You will be able to query this anywhere. Even as an argument to res.render(). See below:

    // app/controllers/tasks.js
    
    // henri has a gql function which does nothing but help editors parse gql...!
    const { gql } = henri;
    
    module.exports = {
      index: async (req, res) => {
        return res.render('/tasks', {
          graphql: gql`
            {
              tasks {
                _id
                reference
                description
                type {
                  _id
                  name
                  prefix
                  counter
                }
                location {
                  _id
                  name
                }
              }
              locations {
                _id
                name
              }
            }
          `,
        });
      },
    };

    Views

    You can use React, Vue and Handlebars as renderer. They are all server-side rendered and the first two options use webpack to push updates to the browser.

    React

    We use next.js to render pages and inject data from controllers. You can only add pages and if the defined routes don't match, and next matches a route, it will be rendered.

    Usage (config file):

    {
      "renderer": "react"
    }

    Example:

    // app/views/pages/log.js
    
    import React from 'react';
    import Link from 'next/link';
    
    export default data => (
      <div>
        <div>{data}</div>
        <Link href="/home">
          <a>Home</a>
        </Link>
      </div>
    );

    You can also add webpack configuration in config/webpack.js:

    // If you want to have jQuery as a global...
    
    module.exports = {
      webpack: async (config, { dev }, webpack) => {
        config.plugins.push(
          new webpack.ProvidePlugin({
            $: 'jquery',
            jQuery: 'jquery',
          })
        );
        return config;
      },
    };

    Inferno

    You can use Inferno instead of React in production. In development, React will be used for hot re/loading.

    Installation:

    yarn add react react-dom inferno inferno-compat inferno-server

    Usage (config file):

    {
      "renderer": "inferno"
    }

    Preact

    You can use Preact instead of React in production. In development, React will be used for hot re/loading.

    Installation:

    yarn add react react-dom preact preact-compat

    Usage (config file):

    {
      "renderer": "preact"
    }

    Vue.js

    We use Nuxt.js to render pages and inject data from controllers. You can only add pages and if the defined routes don't match, and nuxt matches a route, it will be rendered.

    Usage (config file):

    {
      "renderer": "vue"
    }

    Example:

    <template>
      <div>
        <h1>Welcome!</h1>
        <nuxt-link to="/about">About page</nuxt-link>
      </div>
    </template>

    Handlebars

    The handlebars options renders your .html or .hbs files under app/views/pages.

    It will also load partials from app/views/partials

    Usage (config file):

    {
      "renderer": "template"
    }

    Example:

    <html>
    
    <head>
      <title>Hello!</title>
    </head>
    
    <body>
      {{> somePartials }}
      <li>Some data: {{hello}}</li>
    </body>
    
    </html>

    Fetching data again

    You can refetch data from any data-hydrated controller endpoint with GET using the application/json header.

    Controllers

    You can easily add controllers under app/controllers.

    They will be autoloaded and available throughout your application.

    Controllers are auto-reloaded on save.

    // app/controllers/User.js
    
    module.exports = {
      info: async (req, res) => {
        if (!req.isAuthenticated()) {
          return res.status(403).send("Sorry! You can't see that.");
        }
        const { user } = henri;
        if (await User.count({ email: '[email protected]' })) {
          await User.update({ email: '[email protected]' }, { password: 'blue' });
          return res.send('user exists.');
        }
        try {
          await user.compare('moo', pass);
          res.send('logged in!');
        } catch (error) {
          res.send('not good');
        }
      },
      create: (req, res) => {
        await User.create({ email: '[email protected]', password: 'moo' });
      },
      fetch: async (req, res) => {
        const users = await User.find();
        res.send(users);
      },
      postinfo: async (req, res) => {
        let data = req.isAuthenticated() ? await User.find() : {};
        res.render('/log', data);
      }
    };

    Routes

    Routes are defined in config/routes.js. Also, any pages in app/views/pages will be rendered if no routes match before.

    Routes are a simple object with a key standing as a route or an action verb (used by express) and a route.

    If you want to access the res.render data, you can make the call with application/json header. Everything else will be rendered.

    // config/routes.js
    
    module.exports = {
      '/test': 'user#info', // default to 'get /test'
    
      '/abc/:id': 'moo#iii', // as this controller does not exists, route won't be loaded
    
      '/user/find': 'user#fetch',
    
      'get /poo': 'user#postinfo',
    
      'post /poo': 'user#create',
    
      'get /secured': {
        controller: 'secureController#index',
        roles: ['admin'],
      },
    
      'resources todo': {
        controller: 'todo',
      },
    
      'crud categories': {
        scope: 'api',
        controller: 'categories',
        omit: ['destroy'], // DELETE route will not be loaded
      },
    };

    Roles

    You can specify an array of roles which need to be matched to access the routes.

    CRUD

    The crud keyword (instead of http verbs) will create routes in a predefined way:

    // 'crud happy': 'life'
    
    GET /happy => life#index
    POST /happy => life#create
    PATCH /happy/:id => life#update
    PUT /happy/:id => life#update
    DELETE /happy/:id => life#destroy

    Resources

    The resources keyword (instead of http verbs) add views target to CRUD, ending up with:

    // 'resources happy': 'life'
    
    GET /happy => life#index
    POST /happy => life#create
    PATCH /happy/:id => life#update
    PUT /happy/:id => life#update
    DELETE /happy/:id => life#destroy
    
    GET /happy/:id/edit => life#edit
    GET /happy/new => life#new
    GET /happy/:id => life#show

    Scope

    You can add scope to your routes to prefix them with anything you want.

    Omit (crud & resources only)

    You can add omit array to your routes to prevent this route to be created.

    Mail

    We use nodemailer to provide email capabilities.

    When running tests, we use nodemailer's ethereal fake-mail service.

    Config

    {
      "mail": {
        // ...Same as nodemailer's config
      }
    }

    Send

    We provide a wrapper around nodemailer.SendMail:

    await henri.mail.send({
      from: '"Henri Server" <[email protected]>', // sender address
      to: '[email protected], [email protected]', // list of receivers
      subject: 'Hello ?', // Subject line
      text: 'Hello world?', // plain text body
      html: '<b>Hello world?</b>', // html body
    });

    If you are using the test accounts, you will see a link to your email in the console.

    You can access nodemailer's package directly from henri.mail.nodemailer and transporter from henri.mail.transporter.

    Workers

    You can add files under app/workers and they will be auto-loaded, watched and reloaded.

    If they export a start() and a stop() method, they will be call when initializing and tearing down (reload also).

    Example:

    let timer;
    
    const start = h => {
      h.pen.info('worker started');
      timer = setInterval(
        () => h.pen.warn(`the argument is the henri object`),
        5000
      );
    };
    
    const stop = () => clearInterval(timer);
    
    module.exports = { start, stop };

    Under the hood

    Vision

    Bundle the best tools in a structured environment to provide a stable and fast-paced development experience.

    Modules

    We use a 8 levels boot system.

    1. All modules are scanned and put in a sequence with same-level modules

    2. We cycle from level 0 to 7, initializing all the same-level modules in a concurrent way

    3. If the module is reloadable, it will unwind and rewind in the same sequence on reloads

    See the Contributing section for more information

    Plans

    • Add helpers integration
    • Add documentation!
    • Build a website
    • Report bugs!

    Contributing

    • Step by step wiki here

    Thanks to the following and their contributors

    Author

    • Félix-Antoine Paradis (@reel)
    瀏覽 4
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    編輯 分享
    舉報
    評論
    圖片
    表情
    推薦
    點贊
    評論
    收藏
    分享

    手機掃一掃分享

    編輯 分享
    舉報

    <kbd id="5sdj3"></kbd>
    <th id="5sdj3"></th>

  • <dd id="5sdj3"><form id="5sdj3"></form></dd>
    <td id="5sdj3"><form id="5sdj3"><big id="5sdj3"></big></form></td><del id="5sdj3"></del>

  • <dd id="5sdj3"></dd>
    <dfn id="5sdj3"></dfn>
  • <th id="5sdj3"></th>
    <tfoot id="5sdj3"><menuitem id="5sdj3"></menuitem></tfoot>

  • <td id="5sdj3"><form id="5sdj3"><menu id="5sdj3"></menu></form></td>
  • <kbd id="5sdj3"><form id="5sdj3"></form></kbd>
    国产毛片欧美毛片高潮 | 中文字幕在线永久 | 中国老熟女 | 久久爽无码人妻AⅤ精品网址 | 男人的天堂成人网站 |