JavaScript and Typescript
General rules and advices
Nextcloud uses Vue.js for its interface, for a consistent user interface we recommend apps to also use Vue with provided components. Yet also vanilla JavaScript and HTML can be used.
We recommend using Typescript for its type checking and improved static code analysis features.
Do not create global variables, instead if needed use global namespace objects like
OCA.YourApp.…
Use JavaScript strict mode (automatically the case when using JavaScript modules)
ESLint config
There is a shared configuration for eslint that you can use to automatically format your Nextcloud apps’s JavaScript and Typescript code. It consists of two parts: a config package that contains the formatting preferences and a plugin to detect deprecated and removed APIs in your code. See their readmes for instructions.
Filesystem structure
For vanilla JavaScript we recommend the following structure:
appid/
: Root of the appjs/
: JavaScript filesappid.js
: Your app entry point
css/
: Location for all CSS filesappid.css
When using a bundler to compile Typescript (or JavaScript) we recommend the following structure:
appid/
: Root of the appjs/
: Compiled JavaScript filescss/
: Compiled CSS outputsrc/
: Root of all source filescomponents/
: Location of Vue componentscomposables/
: Location of Vue composablesservices/
: Location for service files like API abstractionsstores/
: Location of Pinia storesviews/
: Location of viewsmain.ts
: Main entry point of your app
Filenames
We do not have strict rules for filenames, either kebab case or camel case will work.
Yet we strongly recommend for Vue apps to follow the Vue recommendations and use the same filename as the component name.
E.g. if your component is called AppRoot
then the file should be called AppRoot.vue
.
Code style
General
Naming and casing
Use camelCase for
functions
methods
properties
variables
Use PascalCase for
classes
enums
types
interfaces
Vue components
For readability only capitalize the first letter of abbreviations like
callHttpApi()
instead ofcallHTTPAPI()
.Sub-components should be prefixed. E.g. splitting a component like
FileListEntry
into smaller components calledFileListEntryName
,FileListEntryIcon
…Components should not have single-word names, this could conflict with current or future native HTML tags as these are always single-word. E.g. if you have a settings view, do not call it
Settings
butSettingsView
orUserSettings
etc.
✅ Do |
❌ Don’t |
---|---|
const fileId = 123
const obj = {
myProperty: false,
}
doSomething()
|
const file_id = 123
const obj = {
'my-property': false,
}
do_something()
|
✅ Do |
❌ Don’t |
---|---|
class MyClass { /* ... */ }
interface IRequest { /* ... */ }
type Arguments = string[]
|
class myClass { /* ... */ }
interface I_request { /* ... */ }
type arguments = string[]
|
Indentation
Use tabs instead of spaces for indenting - tab width is 4 spaces.
You can align e.g. comments using spaces if needed.
Semicolons
✅ Do |
❌ Don’t |
---|---|
const text = 'foo'
doSomething()
|
const text = 'foo';
doSomething();
|
const text = 'foo'
;(someProp as SomeType).handle()
|
Strings
✅ Do |
❌ Don’t |
---|---|
const text = 'foo'
|
const text = "foo"
|
✅ Do |
❌ Don’t |
---|---|
const text = `Hello ${username}!`
|
const text = 'Hello ' + username
|
Arrays
✅ Do |
❌ Don’t |
---|---|
const arr = [
'first',
'second',
'third',
]
|
const arr = ['first', 'second', 'third']
|
✅ Do |
❌ Don’t |
---|---|
const arr = [
'first',
'second',
'third',
]
|
const arr = [
'first',
'second',
'third'
]
|
const arr = [
'first',
'second',
+ 'third',
]
|
const arr = [
'first',
- 'second'
+ 'second',
+ 'third'
]
|
Functions
No spaces between function name and parameters.
Braces on same line as the definition.
Use consistent new lines in parameters (either all on one line, or one parameter per line).
For top-level functions, prefer regular functions over arrow functions. In Javascript functions defined with the
function
keyword will be hoisted, thus can even be used in other functions above their definition. Also using thefunction
keyword makes the definition more explicit for readability. For callbacks anonymous arrow functions are often better suited as they do not create their ownthis
binding.Always use parenthesis for arrow functions. This helps for readability and prevents issues if parameters are added.
When using implicit return values in arrow functions with multi-line body use parenthesis around the body.
✅ Do |
❌ Don’t |
---|---|
doSomething(1, false)
|
doSomething (1, false)
|
✅ Do |
❌ Don’t |
---|---|
function foo(name: string): boolean {
// do something
}
|
function foo(name: string): boolean
{
// do something
}
|
function bar(
firstName: string,
lastName: string,
): boolean {
// do something
}
|
function bar(
firstName: string,
lastName: string,
): boolean
{
// do something
}
|
const arrow = (name: string) => {
// do something
}
|
const arrow = (name: string) =>
{
// do something
}
|
✅ Do |
❌ Don’t |
---|---|
function doSomething(num: number, enable: boolean) {
// ...
}
|
function doSomething(num: number,
enable: boolean) {
// ...
}
|
function doSomething(
num: number,
enable: boolean,
) {
// ...
}
|
function doSomething(
num: number, enable: boolean,
) {
// ...
}
|
✅ Do |
❌ Don’t |
---|---|
export function doSomething(num: number, enable: boolean) {
// ...
}
|
export const doSomething = (num: number, enable: boolean) => {
// ...
}
|
someArray.map((item) => item.name)
// or
someArray.map((item) => {
return item.name
})
|
// while this is valid and work
someArray.map(function (item) {
return item.name
})
// there is a caveat with accessing "this"
someArray.map(function (item) {
// "this" is not the previous context
// but the context of the callback function.
// Thus this.category will be undefined.
return `${this.category}: ${item.name}`
})
|
✅ Do |
❌ Don’t |
---|---|
myArray.map((item) => item.name)
|
myArray.map(item => item.name)
|
myArray.map((item, index) => getName(item, index))
|
✅ Do |
❌ Don’t |
---|---|
myArray.map((item) => (
item.value
? 'yes'
: 'no'
))
|
myArray.map((item) => item.value
? 'yes'
: 'no'
)
|
myArray.map((item) => ({
prop: item.value,
other: true,
}))
|
Objects
✅ Do |
❌ Don’t |
---|---|
const obj = {
noQuotesNeeded: true,
'quotes-needed': false,
}
|
const obj = {
'noQuotesNeeded': true,
'quotes-needed': false,
}
|
✅ Do |
❌ Don’t |
---|---|
const name = 'jdoe'
// ...
const obj = {
name,
id: 123,
}
|
const name = 'jdoe'
// ...
const obj = {
name: name,
id: 123,
}
|
✅ Do |
❌ Don’t |
---|---|
const obj = {
first: 1,
second: 'two',
}
|
const obj = { first: 1, second: 'two' }
|
✅ Do |
❌ Don’t |
---|---|
const obj = { prop: true }
|
const obj = {prop: true}
|
✅ Do |
❌ Don’t |
---|---|
const obj = {
first: 1,
second: 2,
}
|
const obj = {
first: 1,
second: 2
}
|
const obj = {
first: 1,
second: 2,
+ third: 3,
}
|
const obj = {
first: 1,
- second: 2
+ second: 2,
+ third: 3
}
|
Operators
Always use
===
and!==
instead of==
and!=
Prefer explicit comparisons
Here’s why:
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
✅ Do |
❌ Don’t |
---|---|
if (array.length > 0) { /* ... */ }
|
if (array.length) { /* ... */ }
|
if (array) { /* this is always true! */ }
|
Control structures
Always use braces, also for one line ifs
Split long ifs into multiple lines
Always use break in switch statements and prevent a default block with warnings if it shouldn’t be accessed
✅ Do |
❌ Don’t |
---|---|
if (myVar === 'hi') {
doSomething()
}
|
if (array.length > 0) doSomething()
|
for (let i = 0; i < 4; i++) {
// your code
}
|
for (let i = 0; i < 4; i++)
// your code
|
✅ Do |
❌ Don’t |
---|---|
if (something === 'something'
|| condition2
&& condition3
) {
// your code
}
|
if (something === 'something' || condition2 && condition3) {
// your code
}
|