Skip to content

Latest commit

 

History

History
277 lines (180 loc) · 16.3 KB

BUILD_TEMPLATE.md

File metadata and controls

277 lines (180 loc) · 16.3 KB

Building a new template

What is a template?

Every template is built on top of Twilio Functions & Assets for logic and static file hosting. A template is essentially a folder in the root of the function-templates repository that follows the same project structure as a regular Serverless Toolkit project with a few specific aspects.

At a bare minimum a template will have the following files:

my-project
├── .env.example
├── .owners
├── assets
│   ├── index.html
├── functions
│   ├── <function-name>.js
├── tests
│   ├── <function-name>.test.js
├── CHANGELOG.md
├── package.json
└── README.md
File(s) About
.env.example Any environment variable that your template relies on should be represented in here. These files support certain comments that help define the environment variables. Check out the schema here.
.owners This file lists which GitHub users should be assigned for PR reviews. For external contributors you can leave the default values. For Twilio teams please add your Github username to the bottom of this file, replacing the comment # Insert your Github username here.
assets/ Any file inside this folder will become a Twilio Asset. You can use the same naming conventions as the Serverless Toolkit to differentiate between private, protected and public Assets
assets/index.html Unless your template has its own UI, every template should use the same index.html that explains customers how to use their new application. Learn more about the index.html file and it's role below.
cypress/ Optional: This folder only appears if you created the template with end to end tests. For more details check out the Testing Guide
functions/ Any JavaScript file inside this folder will result in a Twilio Function. You can use the same naming convention as the Serverless Toolkit to define protected assets or create nested paths.
tests/ Any template uses Jest as a runner for unit tests. Please place any tests only into this directory.
CHANGELOG.md For any major changes please document them in this file to make it easier for customers to identify changes.
package.json Lightweight package.json file. Any file should always include "private": true and a "version": "1.0.0" (adjust version numbers using SemVer as you see fit) as well as a "dependencies": {...} field. Do not include any other fields. devDependencies should only be installed at a repository root level.
README.md The basic structure gets autogenerated. Please fill out the remaining content such as required environment variables while keeping the general structure.

Outside of this directory your template will be referenced both in the templates.json file and the env-variables.manifest.json. If you follow the steps outlined below, you will not have to interact with these files any further. If you duplicate an existing template you will have to adjust the templates.json file manually.

Create a new template

Importing an existing project

If you have an already working Functions & Assets application using the Serverless Toolkit, you'll be able to import it as a template using the command:

npm run import-template -- <path-to-project>

Example:

npm run import-template -- ../my-project

You'll be prompted with some questions after which the template will be bootstraped. From here you can adjust the template using the steps outlined below.

Creating a template from scratch

Creating a new function template requires a couple of steps. While you could perform them all manually the fastest way for you to get started is to run:

npm run new-template

This script will prompt you for a couple of questions and set up a basic template that you can work from.

It will create a directory with the name you specified. In there you'll find a functions/ directory with two functions. A blank.js file with the basic structure of a Twilio Function and a hello-messaging.protected.js that acts as a "protected" Function. Meaning once it's deployed it will not be accessible without a valid X-Twilio-Signature header. Protected Functions are best used to respond to Twilio webhooks.

Locally running your template

  1. Make sure you have the Twilio CLI installed.

  2. Enter the directory of your template. For example:

cd demo
  1. Run the local development server:
twilio serverless:start
  1. If you are using environment variables for your Function, make sure you set them in your normal environment and then run instead the command:
twilio serverless:start --load-local-env
  1. Any changes to Functions & Assets will automatically be loaded. If you do changes to environment variables you'll have to restart the command.

Modifying your template

Once your template is bootstrapped it's time to build.

Creating a new Function

You can create new Functions by creating a new JavaScript file within your functions/ directory of your template. Any subfolders will be part of the resulting path of your deployed Function. Examples:

File path URL Path
<your-template>/functions/hello.js /hello
<your-template>/functions/sms/webhook-handler.js /sms/webhook-handler

Additionally, you can use .protected.js and .private.js as file extensions to mark a Function as protected (meaning once it's deployed it will not be accessible without a valid X-Twilio-Signature header) or private (meaning only other Functions can access the file). These extensions will not be part of the final path. Examples:

File path URL Path
<your-template>/functions/hello.private.js Runtime.getFunctions()['/hello'].path
<your-template>/functions/sms/webhook-handler.protected.js [protected] /sms/webhook-handler

Every Function has to be a valid Twilio Function with a valid handler method. Learn more about the method in the Functions docs.

Adding an Asset

Any static file that is required by your application can be added to your template's assets/ folder. By default every asset is public unless you .protected. or .private. as part of the filename. The .protected. and .private. will be stripped out of the final path during deployment but indicate to the system what visibility the Asset should have. You can learn more about the different visibilities of Functions and Assets in the Docs.

Dealing with shared logic

While shared logic could be stored both in private Functions or private Assets, all templates should store shared logic as private Assets. This makes it easier to differentiate between code that exposes an HTTP endpoint (Functions) and those that don't (Assets). Just make sure your Asset is correctly marked as private Asset

From here you can use it using the Runtime.getAssets() Function.

As an example let's say you have a private Asset called: assets/demo.private.js with the content:

exports.demo = function demo() {
  return 'This is a demo!'
}

Inside your Function you'll be able to use it the following way:

const { demo } = require(Runtime.getAssets()['/demo'].path);
console.log(demo())

Using a Twilio Number

If your template requires a Twilio phone number to function (incoming or outgoing) you need to add the following to your .env.example file:

# description: The Twilio phone number to send broadcast SMS from
# format: phone_number
# required: true
TWILIO_PHONE_NUMBER=

If the template is used with Quick Deploy this will result in CodeExchange showing a Twilio phone number picker before deploying the application.

To use the phone number inside your Twilio Function, for example to start a phone call or send an SMS, you can reference it using context.TWILIO_PHONE_NUMBER.

If a customer uses the template outside of Quick Deploy (for example using the Serverless Toolkit), they'll have to manually set the variable to a valid Twilio Phone Number inside the .env file of the project.

SMS and Voice Webhooks

In case your template exposes a voice or SMS webhook, you have to do two things:

  1. Set the Twilio Function as protected by adding .protected.js as the file extension of your Function
  2. Add the respective TWILIO_SMS_WEBHOOK_URL or TWILIO_VOICE_WEBHOOK_URL to the .env.example file and point it against the right path for the webhook.

For SMS Webhooks:

# description: The path to the webhook
# configurable: false
TWILIO_SMS_WEBHOOK_URL=/forward-message

For Voice webhooks:

# description: The path to the webhook
# configurable: false
TWILIO_VOICE_WEBHOOK_URL=/forward-message

Adding external dependencies (npm)

This project uses npm workspaces to handle dependencies. In order to add a new dependency to your template, you'll need to use the -w flag to specify which workspace (template) you want to install the dependency in. As an example if you want to install the twilio-video npm package in the video-token template you'd run:

npm install twilio-video -w=video-token

Adding environment variables

Function templates can use environment variables for deploy-specific secrets by adding them to the .env file in the root of your template. These are the fields that the user will be able to pre-set on the CodeExchange web app. Step 2 visually shows the env vars that are set in .env: https://www.twilio.com/code-exchange/simple-sms-forwarding

Any variable you want the user to have to set should be added to the .env.example file in your template directory and should include a commented line before that explaining what the variable is about. Example:

# description: The number you want your calls to be forwarded to
# required: true
# format: phone_number
MY_PHONE_NUMBER=

Important: You can find the format of the .env.example file and possible comments as part of this Schema.

They should also be mentioned in the existing table inside the README.md of your template directory.

If you do not want an environment variable to appear on the CodeExchange page, set configurable: false for that variable.

Note: All function templates are checked for the presence of a .env.example or a .env file by npm test. If a test named should have a .env.example (or .env) file fails, ensure that your function template's .env file exists and git add has been used to add it to your commit. If your function template lacks environment variables, commit an empty .env file. If the test is failing due to a directory that is not a function template, add that directory to the excludedPaths variable in test/all-templates.test.js.

The index.html file

The index.html page is where users of Quick Deploy will be redirected to once the application has been deployed to their account. Therefore this should be the page that contains additional information on how to use the application they just set up as well as some basic Troubleshooting tips. The index.html will be copied into the customer's applications and served from their account.

Helpers

This project contains a set of helpers for the index.html inside the docs/static directory for both JavaScript and CSS to provide functionality such as:

  • Style buttons, inputs and general elements on the page based on the Paste design system.
  • Prepend the Base URL of functions to inputs
  • Handle Copy to Clipboard on inputs

They are automatically included in new index.html files but you can also manually add them by adding to your HTML:

<link rel="icon" href="https://twilio-labs.github.io/function-templates/static/v1/favicon.ico">
<link rel="stylesheet" href="https://twilio-labs.github.io/function-templates/static/v1/ce-paste-theme.css">
<script src="https://twilio-labs.github.io/function-templates/static/v1/ce-helpers.js" defer></script>

You are welcome to contribute more shared logic to the ce-helpers.js file inside the docs/static/v1 folder. As a rule of thumb this is a good place for JavaScript logic that is lightweight and not relevant for the actual functionality of the application. By moving it out of the way, customers can better understand what is actually required for the application.

Server-rendered elements

If a template gets deployed with Quick Deploy the following comments will be replaced during deployment with the respective UI content:

<!-- APP_INFO_V2 -->:

APP_INFO_V2

<!-- EDIT_CODE -->:

EDIT_CODE

Every template should have either one of the comments in their index.html regardless of whether they built their own UI or are using a regular index.html file.

Using the runtime-helpers library

One of the default dependencies for new function templates is the runtime-helpers library, which is a Twilio project that provides easy-to-use, tested implementations of various common Function building blocks and utilities. We recommend using the shared runtime-helpers version of a feature whenever it exists. Full documentation for the runtime-helpers API and feature set is available in this reference.

Testing

Every template requires at minimum unit tests for the UI, although for more complex UIs it's also encouraged to add some end-to-end tests using Cypress. For more information check out the dedicated testing guide.

Wrapping it up

Adding yourself to .owners

Each app has a .owners file in its root directory. This file is a list of Github usernames that will be assigned as reviewers on any PR that involves changes to that app. Add your Github username to the bottom of this file, replacing the comment # Insert your Github username here.

Versioning and CHANGELOG.md

Every Quick Deploy app has a version field in its package.json that follows semantic versioning, and a CHANGELOG.md file that uses the keep a changelog format. Initially your app will be at version 1.0.0. If you are updating an app that has already been published to Code Exchange, please increment its version number according to the semantic versioning specification, and update its changelog with the changes you have made to the app since its last version. Versioning your app before it gets deployed by users will help isolate issues in a particular version of the app, and will also enable your app to take advantage of a future update mechanism for deployed apps.

Create your pull request

Once you are done, please open a pull request on the function-templates repository from your fork and fill out the pull request template.

If you are adding a new template please name the pull request after the following convention and update NAME_OF_YOUR_TEMPLATE with the name of your template directory.

feat(templates): add NAME_OF_YOUR_TEMPLATE template