Jack Downson

Jack Downson

1552440780

How you can use Vue.js and Chatkit to build a realtime chat app

Good customer service plays an important role in the growth of any business. In today’s world, it is important to offer some sort of live chat interface so that you can offer prompt responses to the customers who want to ask questions about your business.

If you’re looking to get a live chat system up and running for your customers, Chatkit makes it easy to do with just a few lines of code.

Here’s what the finished application will look like:

Vue.js Chat ui Prerequisites

Before you continue with this tutorial, make sure you have Node.js (version 8 or later) and npm installed on your computer. If not, you can find out how to install it for your operating system here. In addition, you need to have prior experience with building Vue.js applications, but no knowledge of Chatkit is assumed.

Sign up for Chatkit

Head over to the Chatkit page and create a free account or sign in to your existing account. By doing so, you’ll be able to create a new instance for your application and manage your credentials.

Once you’re logged in, create a new Chatkit instance for your application, then locate the Credentials tab on your instance’s dashboard and take note of the Instance Locator and Secret Key as we’ll be using later on. vue chat room

Next, click the Console tab and create a new user on your Chatkit instance. This user will be the support staff assigned to each customer when a new chat session is initialized. The user identifier for this user should be support as shown below:

You can also create users programmatically, but creating users from the dashboard inspector is useful for testing purposes.

Set up the application server

Open up the terminal app on your computer, and create a new customer-service directory for this project. Next cd into it, and run npm init -y to initialize the project with a package.json file. Following that, run the command below to install all the dependencies we’ll be making use of for building the application server:

    npm install express dotenv body-parser cors @pusher/chatkit-server --save

Once the dependencies have been installed, create a new .env file in your project root and add in the credentials retrieved from your Chatkit instance dashboard.

    // .env

    PORT=5200
    CHATKIT_INSTANCE_LOCATOR=<your chatkit instance locator>
    CHATKIT_SECRET_KEY=<your chatkit secret key>

Next, set up a new server.js file and paste in the following code into it:

    // server.js

    require('dotenv').config({ path: '.env' });

    const express = require('express');
    const bodyParser = require('body-parser');
    const cors = require('cors');
    const Chatkit = require('@pusher/chatkit-server');

    const app = express();

    const chatkit = new Chatkit.default({
      instanceLocator: process.env.CHATKIT_INSTANCE_LOCATOR,
      key: process.env.CHATKIT_SECRET_KEY,
    });

    app.use(cors());
    app.use(bodyParser.json());
    app.use(bodyParser.urlencoded({ extended: true }));

    app.post('/users', (req, res) => {
      const { userId } = req.body;

      chatkit
        .createUser({
          id: userId,
          name: userId,
        })
        .then(() => {
          res.sendStatus(201);
        })
        .catch(err => {
          if (err.error === 'services/chatkit/user_already_exists') {
            console.log(`User already exists: ${userId}`);
            res.sendStatus(200);
          } else {
            res.status(err.status).json(err);
          }
        });
    });

    app.post('/authenticate', (req, res) => {
      const authData = chatkit.authenticate({
        userId: req.query.user_id,
      });
      res.status(authData.status).send(authData.body);
    });

    app.set('port', process.env.PORT || 5200);
    const server = app.listen(app.get('port'), () => {
      console.log(`Express running → PORT ${server.address().port}`);
    });

We have two routes on the server: the /users route takes a userId, and creates a Chatkit user through our chatkit instance while the /authenticate route is meant to authenticate each user that tries to connect to our Chatkit instance and respond with a token (returned by chatkit.authenticate) if the request is valid.

That’s all we need to do on the server side. You can start the server on port 5200 by running node server.js in the terminal.

Bootstrap the Vue.js application

We’ll be making use of Vue CLI to bootstrap the application frontend. Install it globally on your machine, then use it to create a new Vue.js app in the root of your project directory. When prompted, use the default preset (babel, eslint).

    npm install -g @vue/cli
    vue create client

Following that, cd into the client folder, and install the additional dependencies that we’ll be making use of on the frontend of the application including the Chatkit client SDK.

    npm install skeleton-css vue-router vue-spinkit @pusher/chatkit-client axios --save

Once the dependencies have been installed, run npm run serve to start the development server on http://localhost:8080.

Set up routing

Our application frontend will have two views: one for the customer and one for the support staff. To switch between the two views, we’ll be making use of vue-router which we already installed in the previous section.

First, create two new files for each view within the client/src directory:

    touch Customer.vue Support.vue

Next, open up client/src/main.js and change it to look like this:

    // client/src/main.js

    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import Customer from './Customer.vue';
    import Support from './Support.vue';
    import App from './App.vue';

    Vue.config.productionTip = false;
    Vue.use(VueRouter);

    const routes = [
      { path: '/', component: Customer },
      { path: '/support', component: Support },
    ];

    const router = new VueRouter({
      routes,
    });

    new Vue({
      el: '#app',
      router,
      render: h => h(App),
    });

After importing the library, as you can see, we’ve set up the router to load the Customer view on the root route, and the Support view on the /support route. We also need to update App.vue so that each view is rendered there:

    // client/src/App.vue
    <template>
      <div id="app" class="App">
        <!-- component matched by the route will render here -->
        <router-view></router-view>
      </div>
    </template>

    <script>
    export default {
      name: 'app',
    }
    </script>

Add the application styles

Before we go further, let’s add some styles for our applications. We’re making use of the skeleton-css boilerplate to add some basic styling, and complementing it with our defined styles in client/src/App.css.

Update client/src/main.js to look like this:

    // client/src/main.js

    import Vue from 'vue';
    import VueRouter from 'vue-router';
    import Customer from './Customer.vue';
    import Support from './Support.vue';
    import App from './App.vue';

    // add the lines below
    import 'skeleton-css/css/normalize.css';
    import 'skeleton-css/css/skeleton.css';
    import './App.css';

    // rest of the code

Then create client/src/App.css and update it to look like this:

    // client/src/App.css

    html {
      box-sizing: border-box;
    }

    *, *::before, *::after {
      box-sizing: inherit;
      margin: 0;
      padding: 0;
    }

    .App {
      text-align: center;
      overflow: hidden;
    }

    svg {
      width: 28px;
      height: 28px;
    }

    input[type="text"]:focus {
      border: 1px solid #300d4f;
    }

    button {
      color: white;
      font-size: 14px;
      border-radius: 2px;
      background-color: #331550;
      border: 1px solid #331550;
      cursor: pointer;
      box-sizing: border-box;
    }

    button:hover {
      color: white;
      background-color: rebeccapurple;
    }

    .chat-widget {
      position: absolute;
      bottom: 40px;
      right: 40px;
      width: 400px;
      border: 1px solid #ccc;
    }

    .chat-header {
      width: 100%;
      height: 60px;
      background-color: #00de72;
      display: flex;
      align-items: center;
      justify-content: center;
    }

    .chat-header h2 {
      font-size: 18px;
      margin-bottom: 0;
    }

    .chat-body {
      height: 350px;
      overflow-y: auto;
      padding: 10px;
    }

    .status-messages {
      text-align: center;
      padding: 5px;
    }

    .message {
      background-color: #f6f6f6;
      clear: both;
      margin-bottom: 15px;
      padding: 10px;
      border-radius: 5px;
      max-width: 80%;
    }

    .message.user {
      float: right;
      background-color: peachpuff;
    }

    .message.support {
      float: left;
      background-color: #ddd;
    }

    .message-form, .message-input {
      width: 100%;
      margin-bottom: 0;
    }

    .message-input {
      border-radius: 0;
      border: none;
      border-top: 1px solid #ccc;
      height: 50px;
      padding: 20px;
      font-size: 16px;
      background-color: #f6f6f6
    }

Set up the customer view

This is where the user will initialize a chat session with a customer service agent. Open up Customer.vue in your editor and update it to look like this:

    // client/src/Customer.vue

    <template>
      <div class="customer-chat">
        <h1>Customer Service</h1>
        <p>
          Customers can interact with support using the chat widget in the
          bottom right corner
        </p>

        <button class="contact-btn">
          Contact Support
        </button>
      </div>
    </template>

    <script>
    export default {
      name: 'Customer',
      data() {
        return {
          title: 'Customer Support',
          userId: '',
          currentUser: null,
          currentRoom: null,
          newMessage: '',
          messages: [],
          isDialogOpen: false,
          isLoading: false,
        }
      },
    }
    </script>

Once the user hits the CONTACT SUPPORT button, a dialog should appear requesting the name of the user. This name will serve as the userId for connecting to our Chatkit instance.

Create a new Dialog.vue file inside the client/src/components directory and populate it with the following contents:

    // client/src/components/Dialog.vue

    <template>
      <div class="dialog-container">
        <div class="dialog">
          <form class="dialog-form" @submit.prevent="handleSubmit">
            <label class="username-label" for="username">
              What is your name?
            </label>
            <input
              id="username"
              class="username-input"
              autofocus
              type="text"
              name="userId"
              :value="username"
              @input="handleInput"
              />
            <button type="submit" class="submit-btn">
              Submit
            </button>
          </form>
        </div>
      </div>
    </template>

    <script>
    export default {
      name: 'Dialog',
      props: {
        username: String,
      },
      methods: {
        handleSubmit() {
          this.$emit('submit-username', this.username);
        },
        handleInput(event) {
          const { name, value } = event.target;
          this.$emit('update-input', name, value);
        }
      }
    }
    </script>

    <style scoped>
    .dialog-container {
      position: absolute;
      top: 0;
      right: 0;
      bottom: 0;
      left: 0;
      background-color: rgba(0, 0, 0, 0.9);
      display: flex;
      justify-content:center;
      align-items: center;
    }

    .dialog {
      width: 500px;
      background-color: white;
      display: flex;
      align-items:  center;
    }

    .dialog-form {
      width: 100%;
      margin-bottom: 0;
      padding: 20px;
    }

    .dialog-form > * {
      display: block;
    }

    .username-label {
      text-align: left;
      font-size: 16px;
    }

    .username-input {
      width: 100%;
    }

    .submit-btn {
      width: 100%;
    }
    </style>

Next, import this component into Customer.vue as follows:

    // client/src/Customer.vue

    <template>
      <div class="customer-chat">
        <!-- [..] -->

        <!-- Show the dialog when clicked -->
        <button @click="showDialog" class="contact-btn">
          Contact Support
        </button>

        <!-- Loading indicator -->
        <Spinner v-if="isLoading" name="three-bounce" color="#300d4f" />

        <Dialog
          v-if="isDialogOpen"
          :username="userId"
          @update-input="handleInput"
          @submit-username="launchChat"
          />
      </div>
    </template>

    <script>
    import Dialog from './components/Dialog.vue';
    import Spinner from 'vue-spinkit';

    export default {
      name: 'Customer',
      components: {
        Dialog,
        Spinner,
      },
      data() {
        return {
          title: 'Customer Support',
          userId: '',
          currentUser: null,
          currentRoom: null,
          newMessage: '',
          messages: [],
          isDialogOpen: false,
          isLoading: false,
        }
      },
    }
    </script>

Notice that, we are listening for the update-input and submit-username events on Dialog and binding those events to the handleInput and launchChat methods respectively. We’ve also bound the click event on the CONTACT SUPPORT button to the showDialog method. These methods have not been created yet, so we must do so in a new methods.js file as shown below:

    // client/src/methods.js

    import Chatkit from '@pusher/chatkit-client';
    import axios from 'axios';

    function showDialog() {
      this.isDialogOpen = !this.isDialogOpen;
    }

    function launchChat() {
      this.isDialogOpen = false;
      this.isLoading = true;

      const { userId } = this;

      if (userId === null || userId.trim() === '') {
        alert('Invalid userId');
      } else {
        axios
          .post('http://localhost:5200/users', { userId })
          .then(() => {
            const tokenProvider = new Chatkit.TokenProvider({
              url: 'http://localhost:5200/authenticate',
            });

            const chatManager = new Chatkit.ChatManager({
              instanceLocator: '<your chatkit instance locator>',
              userId,
              tokenProvider,
            });

            return chatManager.connect().then(currentUser => {
              this.currentUser = currentUser;
              this.isLoading = false;
            });
          })
          .catch(console.error);
      }
    }

    function handleInput(name, value) {
      console.log(name, value);
      this[name] = value;
    }

    export {
      handleInput,
      showDialog,
      launchChat
    };

Now we can import and use these methods in our Customer.vue file as follows:

    <template>
     <!-- [..] -->
    </template>

    <script>
    import Dialog from './components/Dialog.vue';
    import { showDialog, launchChat, handleInput } from './methods.js';

    export default {
      name: 'Customer',
      // [..]
      methods: {
        showDialog,
        handleInput,
        launchChat,
      }
    }
    </script>

Be sure to update the <your chatkit instance locator> placeholder within the launchChat() method before proceeding. Now, you should be able to launch the dialog by clicking the contact support button. If you enter a username and hit SUBMIT, we connect to our Chatkit instance and get a currentUser object which represents the current connected user. At this point, we need to show a chat widget so that the user can begin to chat with the customer service agent.

Let’s create a new component for the chat widget and import it into the Customer view. Create a new ChatWidget.vue file within the client/src/components directory and add the following code to it:

    // client/src/components/ChatWidget.vue

    <template>
      <div class="chat-widget">
        <header class="chat-header">
          <h2>Got Questions? Chat with us</h2>
        </header>
        <section class="chat-body">
          <div v-for="message in messages" :key="message.id">
            <span :class="[ message.senderId === currentUser.id ? 'user' :
            'support']" class="message">{{ message.text }}</span>
          </div>
        </section>

        <form @submit.prevent="handleSubmit" class="message-form">
          <input
            class="message-input"
            autofocus
            name="newMessage"
            placeholder="Compose your message and hit ENTER to send"
            :value="newMessage"
            @input="handleInput"
            />
        </form>
      </div>
    </template>

    <script>
    export default {
      name: 'ChatWidget',
      props: {
        newMessage: String,
        messages: Array,
        currentUser: {
          type: Object,
          required: true,
          default: null,
        },
      },
      methods: {
        handleInput(event) {
          const { name, value } = event.target;
          this.$emit('update-input', name, value);
        },
        handleSubmit() {
          this.$emit('send-message');
        }
      }
    }
    </script>

Next, import it in Customer.vue like this:

    // client/src/Customer.vue

    <template>
      <div class="customer-chat">
        <h1>Customer Service</h1>
        <p>
          Customers can interact with support using the chat widget in the
          bottom right corner
        </p>

        <ChatWidget
          v-if="currentUser"
          :newMessage="newMessage"
          :currentUser="currentUser"
          :messages="messages"
          @send-message="sendMessage"
          @update-input="handleInput"
          />

        <button v-else @click="showDialog" class="contact-btn">
          Contact Support
        </button>

        <Spinner v-if="isLoading" name="three-bounce" color="#300d4f" />

        <Dialog
          v-if="isDialogOpen"
          :username="userId"
          @update-input="handleInput"
          @submit-username="launchChat"
          />
      </div>
    </template>

    <script>
    import Dialog from './components/Dialog.vue';
    import Spinner from 'vue-spinkit';
    import ChatWidget from './components/ChatWidget.vue'
    import { sendMessage, connectToRoom, createRoom, addSupportStaffToRoom, showDialog, launchChat, handleInput } from './methods.js';

    export default {
      name: 'Customer',
      components: {
        Dialog,
        Spinner,
        ChatWidget,
      },
      data() {
        return {
          title: 'Customer Support',
          userId: '',
          currentUser: null,
          currentRoom: null,
          newMessage: '',
          messages: [],
          isDialogOpen: false,
          isLoading: false,
        }
      },
      methods: {
        sendMessage,
        connectToRoom,
        addSupportStaffToRoom,
        createRoom,
        showDialog,
        handleInput,
        launchChat,
      }
    }
    </script>

Notice that we imported a few new methods from methods.js but these haven’t been created yet, so let’s do just that:

    // client/src/methods.js

    // [..]

    function sendMessage() {
      const { newMessage, currentUser, currentRoom } = this;

      if (newMessage.trim() === '') return;

      currentUser.sendMessage({
        text: newMessage,
        roomId: `${currentRoom.id}`,
      });

      this.newMessage = '';
    }

    function connectToRoom(id, messageLimit = 100) {
      this.messages = [];
      const { currentUser } = this;

      return currentUser
        .subscribeToRoom({
          roomId: `${id}`,
          messageLimit,
          hooks: {
            onMessage: message => {
              this.messages = [...this.messages, message];
            },
          },
        })
        .then(currentRoom => {
          this.currentRoom = currentRoom;
        });
    }

    function addSupportStaffToRoom() {
      const { currentRoom, currentUser } = this;

      return currentUser.addUserToRoom({
        userId: 'support',
        roomId: currentRoom.id,
      });
    }

    function createRoom() {
      const { currentUser } = this;

      return currentUser
        .createRoom({
          name: currentUser.name,
          private: true,
        })
        .then(room => this.connectToRoom(room.id, 0))
        .then(() => this.addSupportStaffToRoom());
    }

    // update launchChat
    function launchChat() {
      // [..]

      if (userId === null || userId.trim() === '') {
        alert('Invalid userId');
      } else {
        axios
          .post('http://localhost:5200/users', { userId })
          .then(() => {
            // [..]

            return chatManager.connect().then(currentUser => {
              this.currentUser = currentUser;
              this.isLoading = false;
              // add this line
              return this.createRoom();
            });

          })
          .catch(console.error);
      }
    }

    export {
      sendMessage,
      connectToRoom,
      addSupportStaffToRoom,
      createRoom,
      handleInput,
      showDialog,
      launchChat,
    };

Once the currentUser object is set in the application state, we create a new room for this chat session via the createRoom() method. We’ve opted to make the room private in this instance so that only the user and support staff are able to access it. Once the room is created, we have to connect to it before we can send any messages. This is done via the connectToRoom() method which takes the ID of the room that was created and adds the user to the room.

Chatkit’s room subscription hooks allow us to perform actions when some event occurs in the current room. In this instance, we’ve set up the onMessage hook to append new messages to the messages array so that the new message is displayed in the chat widget.

Finally, we add the support agent to the room via the addSupportStaffToRoom() method which means that the support agent will be able to login to their own interface and see all ongoing conversations instantly.

At this point, you should be able to use the chat widget to send and view messages seamlessly.

Set up the support view

The next phase is to set up the view where the support agent will be able to interact with all customers at once. Open up Support.vue in your code editor and paste the following code into it:

    // client/src/Support.vue

    <template>
      <div class="support-area">
        <aside class="support-sidebar">
          <h3>Users</h3>
          <ul v-for="room in rooms" :key="room.id">
            <li
              class="room"
              :class="[currentRoom && currentRoom.id === room.id ? 'active' : '']"
             @click="connectToRoom(room.id)"
            >
            {{ room.name }}
            </li>
          </ul>
        </aside>
        <section class="support-session">
          <header class="current-chat">
            <h3 v-if="currentRoom">{{ currentRoom.name }}</h3>
            <h3 v-else>Chat</h3>
          </header>
          <div class="chat-session">
            <div v-for="message in messages" :key="message.id">
              <span :class="[ message.senderId === currentUser.id ? 'support' :
              'user']" class="message">{{ message.text }}</span>
            </div>
          </div>
          <form @submit.prevent="sendMessage" class="message-form">
            <input
              class="message-input"
              autofocus
              placeholder="Compose your message and hit ENTER to send"
              v-model="newMessage"
              name="newMessage"
              />
          </form>
        </section>
      </div>
    </template>

    <script>
    import { sendMessage, connectToRoom } from './methods';
    import Chatkit from '@pusher/chatkit-client';
    import axios from 'axios';

    export default {
      name: 'Support',
      data() {
        return {
          newMessage: '',
          currentUser: null,
          currentRoom: null,
          rooms: [],
          messages: [],
        }
      },
      methods: {
        sendMessage,
        connectToRoom,
      },
      mounted() {
        const userId = 'support';

        axios
          .post('http://localhost:5200/users', { userId })
          .then(() => {
            const tokenProvider = new Chatkit.TokenProvider({
              url: 'http://localhost:5200/authenticate',
            });

            const chatManager = new Chatkit.ChatManager({
              instanceLocator: '<your chatkit instance locator>',
              userId,
              tokenProvider,
            });

            return chatManager
              .connect({
                onAddedToRoom: room => {
                  this.rooms = [...this.rooms, room];
                },
              })
              .then(currentUser => {
                this.currentUser = currentUser;
                this.rooms = currentUser.rooms;
                if (this.rooms.length >= 1) {
                  this.connectToRoom(this.rooms[0].id);
                }
              });
          })
          .catch(console.error);
      }
    }
    </script>

    <style>
    .support-area {
      width: 100vw;
      height: 100vh;
      display: flex;
    }

    .support-sidebar {
      width: 20%;
      background-color: #300d4f;
      height: 100%;
    }

    .support-sidebar ul {
      list-style: none;
    }

    .support-sidebar h3 {
      color: white;
      margin-bottom: 0;
      text-align: left;
      padding: 10px 20px;
    }

    .room {
      font-size: 22px;
      color: white;
      cursor: pointer;
      text-align: left;
      padding: 10px 20px;
      margin-bottom: 10px;
    }

    .room:hover {
      color: yellowgreen;
    }

    .room.active {
      background-color: yellowgreen;
      color: white;
    }

    .support-session {
      width: 80%;
      height: 100%;
      display: flex;
      flex-direction: column;
    }

    .current-chat {
      border-bottom: 1px solid #ccc;
      text-align: left;
      padding: 10px 20px;
      display: flex;
    }

    .current-chat h3 {
      margin-bottom: 0;
    }

    .chat-session {
      flex-grow: 1;
      overflow-y: auto;
      padding: 10px;
    }
    </style>

The support agent needs to be able to interact with multiple customers at once, so we have a sidebar where all the connected users are listed, and the main chat area for sending and viewing messages.

We’re immediately connecting to the Chatkit instance on page load (via mounted()) and listing all the connected customers in the sidebar. We can jump between chats by clicking on each room name via connectToRoom() . This method simply connects to the selected room and changes the value of currentRoom so that the screen is updated appropriately.

Thanks to the onAddedToRoom() connection hook, we do not need to refresh the support view to see new chats that have been initiated. Everything is updated in realtime without much effort on your part.

Before testing, make sure to update the <your chatkit instance locator> placeholder within the mounted() method.

Wrap up

In this tutorial, I’ve shown you how to set up a customer support application using Vue.js and Chatkit. You can checkout other things Chatkit can do by viewing its extensive documentation. Don’t forget to grab the full source code used for this tutorial in this GitHub repository.

30s ad

Ethereum and Solidity: Build Dapp with VueJS

Learn Web Development Using VueJS

Curso de VueJS 2

Vuejs 2 + Vuex con TypeScript Nivel PRO

Curso de Vuejs 2, Cognito y GraphQL

#vue-js #javascript #chatbot #vuejs

What is GEEK

Buddha Community

How you can use Vue.js and Chatkit to build a realtime chat app
Chloe  Butler

Chloe Butler

1667425440

Pdf2gerb: Perl Script Converts PDF Files to Gerber format

pdf2gerb

Perl script converts PDF files to Gerber format

Pdf2Gerb generates Gerber 274X photoplotting and Excellon drill files from PDFs of a PCB. Up to three PDFs are used: the top copper layer, the bottom copper layer (for 2-sided PCBs), and an optional silk screen layer. The PDFs can be created directly from any PDF drawing software, or a PDF print driver can be used to capture the Print output if the drawing software does not directly support output to PDF.

The general workflow is as follows:

  1. Design the PCB using your favorite CAD or drawing software.
  2. Print the top and bottom copper and top silk screen layers to a PDF file.
  3. Run Pdf2Gerb on the PDFs to create Gerber and Excellon files.
  4. Use a Gerber viewer to double-check the output against the original PCB design.
  5. Make adjustments as needed.
  6. Submit the files to a PCB manufacturer.

Please note that Pdf2Gerb does NOT perform DRC (Design Rule Checks), as these will vary according to individual PCB manufacturer conventions and capabilities. Also note that Pdf2Gerb is not perfect, so the output files must always be checked before submitting them. As of version 1.6, Pdf2Gerb supports most PCB elements, such as round and square pads, round holes, traces, SMD pads, ground planes, no-fill areas, and panelization. However, because it interprets the graphical output of a Print function, there are limitations in what it can recognize (or there may be bugs).

See docs/Pdf2Gerb.pdf for install/setup, config, usage, and other info.


pdf2gerb_cfg.pm

#Pdf2Gerb config settings:
#Put this file in same folder/directory as pdf2gerb.pl itself (global settings),
#or copy to another folder/directory with PDFs if you want PCB-specific settings.
#There is only one user of this file, so we don't need a custom package or namespace.
#NOTE: all constants defined in here will be added to main namespace.
#package pdf2gerb_cfg;

use strict; #trap undef vars (easier debug)
use warnings; #other useful info (easier debug)


##############################################################################################
#configurable settings:
#change values here instead of in main pfg2gerb.pl file

use constant WANT_COLORS => ($^O !~ m/Win/); #ANSI colors no worky on Windows? this must be set < first DebugPrint() call

#just a little warning; set realistic expectations:
#DebugPrint("${\(CYAN)}Pdf2Gerb.pl ${\(VERSION)}, $^O O/S\n${\(YELLOW)}${\(BOLD)}${\(ITALIC)}This is EXPERIMENTAL software.  \nGerber files MAY CONTAIN ERRORS.  Please CHECK them before fabrication!${\(RESET)}", 0); #if WANT_DEBUG

use constant METRIC => FALSE; #set to TRUE for metric units (only affect final numbers in output files, not internal arithmetic)
use constant APERTURE_LIMIT => 0; #34; #max #apertures to use; generate warnings if too many apertures are used (0 to not check)
use constant DRILL_FMT => '2.4'; #'2.3'; #'2.4' is the default for PCB fab; change to '2.3' for CNC

use constant WANT_DEBUG => 0; #10; #level of debug wanted; higher == more, lower == less, 0 == none
use constant GERBER_DEBUG => 0; #level of debug to include in Gerber file; DON'T USE FOR FABRICATION
use constant WANT_STREAMS => FALSE; #TRUE; #save decompressed streams to files (for debug)
use constant WANT_ALLINPUT => FALSE; #TRUE; #save entire input stream (for debug ONLY)

#DebugPrint(sprintf("${\(CYAN)}DEBUG: stdout %d, gerber %d, want streams? %d, all input? %d, O/S: $^O, Perl: $]${\(RESET)}\n", WANT_DEBUG, GERBER_DEBUG, WANT_STREAMS, WANT_ALLINPUT), 1);
#DebugPrint(sprintf("max int = %d, min int = %d\n", MAXINT, MININT), 1); 

#define standard trace and pad sizes to reduce scaling or PDF rendering errors:
#This avoids weird aperture settings and replaces them with more standardized values.
#(I'm not sure how photoplotters handle strange sizes).
#Fewer choices here gives more accurate mapping in the final Gerber files.
#units are in inches
use constant TOOL_SIZES => #add more as desired
(
#round or square pads (> 0) and drills (< 0):
    .010, -.001,  #tiny pads for SMD; dummy drill size (too small for practical use, but needed so StandardTool will use this entry)
    .031, -.014,  #used for vias
    .041, -.020,  #smallest non-filled plated hole
    .051, -.025,
    .056, -.029,  #useful for IC pins
    .070, -.033,
    .075, -.040,  #heavier leads
#    .090, -.043,  #NOTE: 600 dpi is not high enough resolution to reliably distinguish between .043" and .046", so choose 1 of the 2 here
    .100, -.046,
    .115, -.052,
    .130, -.061,
    .140, -.067,
    .150, -.079,
    .175, -.088,
    .190, -.093,
    .200, -.100,
    .220, -.110,
    .160, -.125,  #useful for mounting holes
#some additional pad sizes without holes (repeat a previous hole size if you just want the pad size):
    .090, -.040,  #want a .090 pad option, but use dummy hole size
    .065, -.040, #.065 x .065 rect pad
    .035, -.040, #.035 x .065 rect pad
#traces:
    .001,  #too thin for real traces; use only for board outlines
    .006,  #minimum real trace width; mainly used for text
    .008,  #mainly used for mid-sized text, not traces
    .010,  #minimum recommended trace width for low-current signals
    .012,
    .015,  #moderate low-voltage current
    .020,  #heavier trace for power, ground (even if a lighter one is adequate)
    .025,
    .030,  #heavy-current traces; be careful with these ones!
    .040,
    .050,
    .060,
    .080,
    .100,
    .120,
);
#Areas larger than the values below will be filled with parallel lines:
#This cuts down on the number of aperture sizes used.
#Set to 0 to always use an aperture or drill, regardless of size.
use constant { MAX_APERTURE => max((TOOL_SIZES)) + .004, MAX_DRILL => -min((TOOL_SIZES)) + .004 }; #max aperture and drill sizes (plus a little tolerance)
#DebugPrint(sprintf("using %d standard tool sizes: %s, max aper %.3f, max drill %.3f\n", scalar((TOOL_SIZES)), join(", ", (TOOL_SIZES)), MAX_APERTURE, MAX_DRILL), 1);

#NOTE: Compare the PDF to the original CAD file to check the accuracy of the PDF rendering and parsing!
#for example, the CAD software I used generated the following circles for holes:
#CAD hole size:   parsed PDF diameter:      error:
#  .014                .016                +.002
#  .020                .02267              +.00267
#  .025                .026                +.001
#  .029                .03167              +.00267
#  .033                .036                +.003
#  .040                .04267              +.00267
#This was usually ~ .002" - .003" too big compared to the hole as displayed in the CAD software.
#To compensate for PDF rendering errors (either during CAD Print function or PDF parsing logic), adjust the values below as needed.
#units are pixels; for example, a value of 2.4 at 600 dpi = .0004 inch, 2 at 600 dpi = .0033"
use constant
{
    HOLE_ADJUST => -0.004 * 600, #-2.6, #holes seemed to be slightly oversized (by .002" - .004"), so shrink them a little
    RNDPAD_ADJUST => -0.003 * 600, #-2, #-2.4, #round pads seemed to be slightly oversized, so shrink them a little
    SQRPAD_ADJUST => +0.001 * 600, #+.5, #square pads are sometimes too small by .00067, so bump them up a little
    RECTPAD_ADJUST => 0, #(pixels) rectangular pads seem to be okay? (not tested much)
    TRACE_ADJUST => 0, #(pixels) traces seemed to be okay?
    REDUCE_TOLERANCE => .001, #(inches) allow this much variation when reducing circles and rects
};

#Also, my CAD's Print function or the PDF print driver I used was a little off for circles, so define some additional adjustment values here:
#Values are added to X/Y coordinates; units are pixels; for example, a value of 1 at 600 dpi would be ~= .002 inch
use constant
{
    CIRCLE_ADJUST_MINX => 0,
    CIRCLE_ADJUST_MINY => -0.001 * 600, #-1, #circles were a little too high, so nudge them a little lower
    CIRCLE_ADJUST_MAXX => +0.001 * 600, #+1, #circles were a little too far to the left, so nudge them a little to the right
    CIRCLE_ADJUST_MAXY => 0,
    SUBST_CIRCLE_CLIPRECT => FALSE, #generate circle and substitute for clip rects (to compensate for the way some CAD software draws circles)
    WANT_CLIPRECT => TRUE, #FALSE, #AI doesn't need clip rect at all? should be on normally?
    RECT_COMPLETION => FALSE, #TRUE, #fill in 4th side of rect when 3 sides found
};

#allow .012 clearance around pads for solder mask:
#This value effectively adjusts pad sizes in the TOOL_SIZES list above (only for solder mask layers).
use constant SOLDER_MARGIN => +.012; #units are inches

#line join/cap styles:
use constant
{
    CAP_NONE => 0, #butt (none); line is exact length
    CAP_ROUND => 1, #round cap/join; line overhangs by a semi-circle at either end
    CAP_SQUARE => 2, #square cap/join; line overhangs by a half square on either end
    CAP_OVERRIDE => FALSE, #cap style overrides drawing logic
};
    
#number of elements in each shape type:
use constant
{
    RECT_SHAPELEN => 6, #x0, y0, x1, y1, count, "rect" (start, end corners)
    LINE_SHAPELEN => 6, #x0, y0, x1, y1, count, "line" (line seg)
    CURVE_SHAPELEN => 10, #xstart, ystart, x0, y0, x1, y1, xend, yend, count, "curve" (bezier 2 points)
    CIRCLE_SHAPELEN => 5, #x, y, 5, count, "circle" (center + radius)
};
#const my %SHAPELEN =
#Readonly my %SHAPELEN =>
our %SHAPELEN =
(
    rect => RECT_SHAPELEN,
    line => LINE_SHAPELEN,
    curve => CURVE_SHAPELEN,
    circle => CIRCLE_SHAPELEN,
);

#panelization:
#This will repeat the entire body the number of times indicated along the X or Y axes (files grow accordingly).
#Display elements that overhang PCB boundary can be squashed or left as-is (typically text or other silk screen markings).
#Set "overhangs" TRUE to allow overhangs, FALSE to truncate them.
#xpad and ypad allow margins to be added around outer edge of panelized PCB.
use constant PANELIZE => {'x' => 1, 'y' => 1, 'xpad' => 0, 'ypad' => 0, 'overhangs' => TRUE}; #number of times to repeat in X and Y directions

# Set this to 1 if you need TurboCAD support.
#$turboCAD = FALSE; #is this still needed as an option?

#CIRCAD pad generation uses an appropriate aperture, then moves it (stroke) "a little" - we use this to find pads and distinguish them from PCB holes. 
use constant PAD_STROKE => 0.3; #0.0005 * 600; #units are pixels
#convert very short traces to pads or holes:
use constant TRACE_MINLEN => .001; #units are inches
#use constant ALWAYS_XY => TRUE; #FALSE; #force XY even if X or Y doesn't change; NOTE: needs to be TRUE for all pads to show in FlatCAM and ViewPlot
use constant REMOVE_POLARITY => FALSE; #TRUE; #set to remove subtractive (negative) polarity; NOTE: must be FALSE for ground planes

#PDF uses "points", each point = 1/72 inch
#combined with a PDF scale factor of .12, this gives 600 dpi resolution (1/72 * .12 = 600 dpi)
use constant INCHES_PER_POINT => 1/72; #0.0138888889; #multiply point-size by this to get inches

# The precision used when computing a bezier curve. Higher numbers are more precise but slower (and generate larger files).
#$bezierPrecision = 100;
use constant BEZIER_PRECISION => 36; #100; #use const; reduced for faster rendering (mainly used for silk screen and thermal pads)

# Ground planes and silk screen or larger copper rectangles or circles are filled line-by-line using this resolution.
use constant FILL_WIDTH => .01; #fill at most 0.01 inch at a time

# The max number of characters to read into memory
use constant MAX_BYTES => 10 * M; #bumped up to 10 MB, use const

use constant DUP_DRILL1 => TRUE; #FALSE; #kludge: ViewPlot doesn't load drill files that are too small so duplicate first tool

my $runtime = time(); #Time::HiRes::gettimeofday(); #measure my execution time

print STDERR "Loaded config settings from '${\(__FILE__)}'.\n";
1; #last value must be truthful to indicate successful load


#############################################################################################
#junk/experiment:

#use Package::Constants;
#use Exporter qw(import); #https://perldoc.perl.org/Exporter.html

#my $caller = "pdf2gerb::";

#sub cfg
#{
#    my $proto = shift;
#    my $class = ref($proto) || $proto;
#    my $settings =
#    {
#        $WANT_DEBUG => 990, #10; #level of debug wanted; higher == more, lower == less, 0 == none
#    };
#    bless($settings, $class);
#    return $settings;
#}

#use constant HELLO => "hi there2"; #"main::HELLO" => "hi there";
#use constant GOODBYE => 14; #"main::GOODBYE" => 12;

#print STDERR "read cfg file\n";

#our @EXPORT_OK = Package::Constants->list(__PACKAGE__); #https://www.perlmonks.org/?node_id=1072691; NOTE: "_OK" skips short/common names

#print STDERR scalar(@EXPORT_OK) . " consts exported:\n";
#foreach(@EXPORT_OK) { print STDERR "$_\n"; }
#my $val = main::thing("xyz");
#print STDERR "caller gave me $val\n";
#foreach my $arg (@ARGV) { print STDERR "arg $arg\n"; }

Download Details:

Author: swannman
Source Code: https://github.com/swannman/pdf2gerb

License: GPL-3.0 license

#perl 

Aria Barnes

Aria Barnes

1625232484

Why is Vue JS the most Preferred Choice for Responsive Web Application Development?

For more than two decades, JavaScript has facilitated businesses to develop responsive web applications for their customers. Used both client and server-side, JavaScript enables you to bring dynamics to pages through expanded functionality and real-time modifications.

Did you know!

According to a web development survey 2020, JavaScript is the most used language for the 8th year, with 67.7% of people choosing it. With this came up several javascript frameworks for frontend, backend development, or even testing.

And one such framework is Vue.Js. It is used to build simple projects and can also be advanced to create sophisticated apps using state-of-the-art tools. Beyond that, some other solid reasons give Vuejs a thumbs up for responsive web application development.

Want to know them? Then follow this blog until the end. Through this article, I will describe all the reasons and benefits of Vue js development. So, stay tuned.

Vue.Js - A Brief Introduction

Released in the year 2014 for public use, Vue.Js is an open-source JavaScript framework used to create UIs and single-page applications. It has over 77.4 million likes on Github for creating intuitive web interfaces.

The recent version is Vue.js 2.6, and is the second most preferred framework according to Stack Overflow Developer Survey 2019.

Every Vue.js development company is widely using the framework across the world for responsive web application development. It is centered around the view layer, provides a lot of functionality for the view layer, and builds single-page web applications.

Some most astonishing stats about Vue.Js:

• Vue was ranked #2 in the Front End JavaScript Framework rankings in the State of JS 2019 survey by developers.

• Approximately 427k to 693k sites are built with Vue js, according to Wappalyzer and BuiltWith statistics of June 2020.

• According to the State of JS 2019 survey, 40.5% of JavaScript developers are currently using Vue, while 34.5% have shown keen interest in using it in the future.

• In Stack Overflow's Developer Survey 2020, Vue was ranked the 3rd most popular front-end JavaScript framework.

Why is Vue.Js so popular?

• High-speed run-time performance
• Vue.Js uses a virtual DOM.
• The main focus is on the core library, while the collaborating libraries handle other features such as global state management and routing.
• Vue.JS provides responsive visual components.

Top 7 Reasons to Choose Vue JS for Web Application Development

Vue js development has certain benefits, which will encourage you to use it in your projects. For example, Vue.js is similar to Angular and React in many aspects, and it continues to enjoy increasing popularity compared to other frameworks.

The framework is only 20 kilobytes in size, making it easy for you to download files instantly. Vue.js easily beats other frameworks when it comes to loading times and usage.

Take a look at the compelling advantages of using Vue.Js for web app development.

#1 Simple Integration

Vue.Js is popular because it allows you to integrate Vue.js into other frameworks such as React, enabling you to customize the project as per your needs and requirements.

It helps you build apps with Vue.js from scratch and introduce Vue.js elements into their existing apps. Due to its ease of integration, Vue.js is becoming a popular choice for web development as it can be used with various existing web applications.

You can feel free to include Vue.js CDN and start using it. Most third-party Vue components and libraries are additionally accessible and supported with the Vue.js CDN.

You don't need to set up node and npm to start using Vue.js. This implies that it helps develop new web applications, just like modifying previous applications.

The diversity of components allows you to create different types of web applications and replace existing frameworks. In addition, you can also choose to hire Vue js developers to use the technology to experiment with many other JavaScript applications.

#2 Easy to Understand

One of the main reasons for the growing popularity of Vue.Js is that the framework is straightforward to understand for individuals. This means that you can easily add Vue.Js to your web projects.

Also, Vue.Js has a well-defined architecture for storing your data with life-cycle and custom methods. Vue.Js also provides additional features such as watchers, directives, and computed properties, making it extremely easy to build modern apps and web applications with ease.

Another significant advantage of using the Vue.Js framework is that it makes it easy to build small and large-scale web applications in the shortest amount of time.

#3 Well-defined Ecosystem

The VueJS ecosystem is vibrant and well-defined, allowing Vue.Js development company to switch users to VueJS over other frameworks for web app development.

Without spending hours, you can easily find solutions to your problems. Furthermore, VueJs lets you choose only the building blocks you need.

Although the main focus of Vue is the view layer, with the help of Vue Router, Vue Test Utils, Vuex, and Vue CLI, you can find solutions and recommendations for frequently occurring problems.

The problems fall into these categories, and hence it becomes easy for programmers to get started with coding right away and not waste time figuring out how to use these tools.

The Vue ecosystem is easy to customize and scales between a library and a framework. Compared to other frameworks, its development speed is excellent, and it can also integrate different projects. This is the reason why most website development companies also prefer the Vue.Js ecosystem over others.

#4 Flexibility

Another benefit of going with Vue.Js for web app development needs is flexibility. Vue.Js provides an excellent level of flexibility. And makes it easier for web app development companies to write their templates in HTML, JavaScript, or pure JavaScript using virtual nodes.

Another significant benefit of using Vue.Js is that it makes it easier for developers to work with tools like templating engines, CSS preprocessors, and type checking tools like TypeScript.

#5 Two-Way Communication

Vue.Js is an excellent option for you because it encourages two-way communication. This has become possible with the MVVM architecture to handle HTML blocks. In this way, Vue.Js is very similar to Angular.Js, making it easier to handle HTML blocks as well.

With Vue.Js, two-way data binding is straightforward. This means that any changes made by the developer to the UI are passed to the data, and the changes made to the data are reflected in the UI.

This is also one reason why Vue.Js is also known as reactive because it can react to changes made to the data. This sets it apart from other libraries such as React.Js, which are designed to support only one-way communication.

#6 Detailed Documentation

One essential thing is well-defined documentation that helps you understand the required mechanism and build your application with ease. It shows all the options offered by the framework and related best practice examples.

Vue has excellent docs, and its API references are one of the best in the industry. They are well written, clear, and accessible in dealing with everything you need to know to build a Vue application.

Besides, the documentation at Vue.js is constantly improved and updated. It also includes a simple introductory guide and an excellent overview of the API. Perhaps, this is one of the most detailed documentation available for this type of language.

#7 Large Community Support

Support for the platform is impressive. In 2018, support continued to impress as every question was answered diligently. Over 6,200 problems were solved with an average resolution time of just six hours.

To support the community, there are frequent release cycles of updated information. Furthermore, the community continues to grow and develop with backend support from developers.



Wrapping Up

VueJS is an incredible choice for responsive web app development. Since it is lightweight and user-friendly, it builds a fast and integrated web application. The capabilities and potential of VueJS for web app development are extensive.

While Vuejs is simple to get started with, using it to build scalable web apps requires professionalism. Hence, you can approach a top Vue js development company in India to develop high-performing web apps.

Equipped with all the above features, it doesn't matter whether you want to build a small concept app or a full-fledged web app; Vue.Js is the most performant you can rely on.

Original source

 

#vue js development company #vue js development company in india #vue js development company india #vue js development services #vue js development #vue js development companies

Luna  Mosciski

Luna Mosciski

1600583123

8 Popular Websites That Use The Vue.JS Framework

In this article, we are going to list out the most popular websites using Vue JS as their frontend framework.

Vue JS is one of those elite progressive JavaScript frameworks that has huge demand in the web development industry. Many popular websites are developed using Vue in their frontend development because of its imperative features.

This framework was created by Evan You and still it is maintained by his private team members. Vue is of course an open-source framework which is based on MVVM concept (Model-view view-Model) and used extensively in building sublime user-interfaces and also considered a prime choice for developing single-page heavy applications.

Released in February 2014, Vue JS has gained 64,828 stars on Github, making it very popular in recent times.

Evan used Angular JS on many operations while working for Google and integrated many features in Vue to cover the flaws of Angular.

“I figured, what if I could just extract the part that I really liked about Angular and build something really lightweight." - Evan You

#vuejs #vue #vue-with-laravel #vue-top-story #vue-3 #build-vue-frontend #vue-in-laravel #vue.js

Top VueJS App Development Company in USA

AppClues Infotech is the best & most reliable VueJS App Development Company in USA that builds high-quality and top-notch mobile apps with advanced methodology. The company is focused on providing innovative & technology-oriented solutions as per your specific business needs.

The organization’s VueJS developers have high experience and we have the capability of handling small to big projects. Being one of the leading mobile app development company in USA we are using the latest programming languages and technologies for their clients.

Key Elements:

· Total year of experience - 8+

· Employees Strength - 120+

· Hourly Rate - $25 – $45 / hr

· Location - New York, USA

· Successfully launched projects - 450+

VueJS Development Services by AppClues Infotech

· Custom VueJS Development

· Portal Development Solutions

· Web Application Development

· VueJS Plugin Development

· VueJS Ecommerce Development

· SPA (Single Page App) Development

· VueJS Migration

Why Hire VueJS Developers from AppClues Infotech?

· Agile & Adaptive Development

· 8+ Years of Average Experience

· 100% Transparency

· Guaranteed Bug-free VueJS Solution

· Flexible Engagement Models

· On-Time Project Delivery

· Immediate Technical Support

If you have any project ideas for VueJS app development then share your requirements with AppClues Infotech to get the best solution for your dream projects.

For more info:
Share Yoru Requirements: https://www.appcluesinfotech.com/contact-us/
Email: info@appcluesinfotech.com
Call: +1-978-309-9910
**

#top vue.js development company #vue.js app development company #best vue js development company #hire top vue js developers #hire top vue.js developers in usa #vue js development company usa

NBB: Ad-hoc CLJS Scripting on Node.js

Nbb

Not babashka. Node.js babashka!?

Ad-hoc CLJS scripting on Node.js.

Status

Experimental. Please report issues here.

Goals and features

Nbb's main goal is to make it easy to get started with ad hoc CLJS scripting on Node.js.

Additional goals and features are:

  • Fast startup without relying on a custom version of Node.js.
  • Small artifact (current size is around 1.2MB).
  • First class macros.
  • Support building small TUI apps using Reagent.
  • Complement babashka with libraries from the Node.js ecosystem.

Requirements

Nbb requires Node.js v12 or newer.

How does this tool work?

CLJS code is evaluated through SCI, the same interpreter that powers babashka. Because SCI works with advanced compilation, the bundle size, especially when combined with other dependencies, is smaller than what you get with self-hosted CLJS. That makes startup faster. The trade-off is that execution is less performant and that only a subset of CLJS is available (e.g. no deftype, yet).

Usage

Install nbb from NPM:

$ npm install nbb -g

Omit -g for a local install.

Try out an expression:

$ nbb -e '(+ 1 2 3)'
6

And then install some other NPM libraries to use in the script. E.g.:

$ npm install csv-parse shelljs zx

Create a script which uses the NPM libraries:

(ns script
  (:require ["csv-parse/lib/sync$default" :as csv-parse]
            ["fs" :as fs]
            ["path" :as path]
            ["shelljs$default" :as sh]
            ["term-size$default" :as term-size]
            ["zx$default" :as zx]
            ["zx$fs" :as zxfs]
            [nbb.core :refer [*file*]]))

(prn (path/resolve "."))

(prn (term-size))

(println (count (str (fs/readFileSync *file*))))

(prn (sh/ls "."))

(prn (csv-parse "foo,bar"))

(prn (zxfs/existsSync *file*))

(zx/$ #js ["ls"])

Call the script:

$ nbb script.cljs
"/private/tmp/test-script"
#js {:columns 216, :rows 47}
510
#js ["node_modules" "package-lock.json" "package.json" "script.cljs"]
#js [#js ["foo" "bar"]]
true
$ ls
node_modules
package-lock.json
package.json
script.cljs

Macros

Nbb has first class support for macros: you can define them right inside your .cljs file, like you are used to from JVM Clojure. Consider the plet macro to make working with promises more palatable:

(defmacro plet
  [bindings & body]
  (let [binding-pairs (reverse (partition 2 bindings))
        body (cons 'do body)]
    (reduce (fn [body [sym expr]]
              (let [expr (list '.resolve 'js/Promise expr)]
                (list '.then expr (list 'clojure.core/fn (vector sym)
                                        body))))
            body
            binding-pairs)))

Using this macro we can look async code more like sync code. Consider this puppeteer example:

(-> (.launch puppeteer)
      (.then (fn [browser]
               (-> (.newPage browser)
                   (.then (fn [page]
                            (-> (.goto page "https://clojure.org")
                                (.then #(.screenshot page #js{:path "screenshot.png"}))
                                (.catch #(js/console.log %))
                                (.then #(.close browser)))))))))

Using plet this becomes:

(plet [browser (.launch puppeteer)
       page (.newPage browser)
       _ (.goto page "https://clojure.org")
       _ (-> (.screenshot page #js{:path "screenshot.png"})
             (.catch #(js/console.log %)))]
      (.close browser))

See the puppeteer example for the full code.

Since v0.0.36, nbb includes promesa which is a library to deal with promises. The above plet macro is similar to promesa.core/let.

Startup time

$ time nbb -e '(+ 1 2 3)'
6
nbb -e '(+ 1 2 3)'   0.17s  user 0.02s system 109% cpu 0.168 total

The baseline startup time for a script is about 170ms seconds on my laptop. When invoked via npx this adds another 300ms or so, so for faster startup, either use a globally installed nbb or use $(npm bin)/nbb script.cljs to bypass npx.

Dependencies

NPM dependencies

Nbb does not depend on any NPM dependencies. All NPM libraries loaded by a script are resolved relative to that script. When using the Reagent module, React is resolved in the same way as any other NPM library.

Classpath

To load .cljs files from local paths or dependencies, you can use the --classpath argument. The current dir is added to the classpath automatically. So if there is a file foo/bar.cljs relative to your current dir, then you can load it via (:require [foo.bar :as fb]). Note that nbb uses the same naming conventions for namespaces and directories as other Clojure tools: foo-bar in the namespace name becomes foo_bar in the directory name.

To load dependencies from the Clojure ecosystem, you can use the Clojure CLI or babashka to download them and produce a classpath:

$ classpath="$(clojure -A:nbb -Spath -Sdeps '{:aliases {:nbb {:replace-deps {com.github.seancorfield/honeysql {:git/tag "v2.0.0-rc5" :git/sha "01c3a55"}}}}}')"

and then feed it to the --classpath argument:

$ nbb --classpath "$classpath" -e "(require '[honey.sql :as sql]) (sql/format {:select :foo :from :bar :where [:= :baz 2]})"
["SELECT foo FROM bar WHERE baz = ?" 2]

Currently nbb only reads from directories, not jar files, so you are encouraged to use git libs. Support for .jar files will be added later.

Current file

The name of the file that is currently being executed is available via nbb.core/*file* or on the metadata of vars:

(ns foo
  (:require [nbb.core :refer [*file*]]))

(prn *file*) ;; "/private/tmp/foo.cljs"

(defn f [])
(prn (:file (meta #'f))) ;; "/private/tmp/foo.cljs"

Reagent

Nbb includes reagent.core which will be lazily loaded when required. You can use this together with ink to create a TUI application:

$ npm install ink

ink-demo.cljs:

(ns ink-demo
  (:require ["ink" :refer [render Text]]
            [reagent.core :as r]))

(defonce state (r/atom 0))

(doseq [n (range 1 11)]
  (js/setTimeout #(swap! state inc) (* n 500)))

(defn hello []
  [:> Text {:color "green"} "Hello, world! " @state])

(render (r/as-element [hello]))

Promesa

Working with callbacks and promises can become tedious. Since nbb v0.0.36 the promesa.core namespace is included with the let and do! macros. An example:

(ns prom
  (:require [promesa.core :as p]))

(defn sleep [ms]
  (js/Promise.
   (fn [resolve _]
     (js/setTimeout resolve ms))))

(defn do-stuff
  []
  (p/do!
   (println "Doing stuff which takes a while")
   (sleep 1000)
   1))

(p/let [a (do-stuff)
        b (inc a)
        c (do-stuff)
        d (+ b c)]
  (prn d))
$ nbb prom.cljs
Doing stuff which takes a while
Doing stuff which takes a while
3

Also see API docs.

Js-interop

Since nbb v0.0.75 applied-science/js-interop is available:

(ns example
  (:require [applied-science.js-interop :as j]))

(def o (j/lit {:a 1 :b 2 :c {:d 1}}))

(prn (j/select-keys o [:a :b])) ;; #js {:a 1, :b 2}
(prn (j/get-in o [:c :d])) ;; 1

Most of this library is supported in nbb, except the following:

  • destructuring using :syms
  • property access using .-x notation. In nbb, you must use keywords.

See the example of what is currently supported.

Examples

See the examples directory for small examples.

Also check out these projects built with nbb:

API

See API documentation.

Migrating to shadow-cljs

See this gist on how to convert an nbb script or project to shadow-cljs.

Build

Prequisites:

  • babashka >= 0.4.0
  • Clojure CLI >= 1.10.3.933
  • Node.js 16.5.0 (lower version may work, but this is the one I used to build)

To build:

  • Clone and cd into this repo
  • bb release

Run bb tasks for more project-related tasks.

Download Details:
Author: borkdude
Download Link: Download The Source Code
Official Website: https://github.com/borkdude/nbb 
License: EPL-1.0

#node #javascript