Tagged “vue”
-
EACCES Error with Vue using Vite
vite
vue
-
Migrating a Vue 3 Project from Vue CLI to Vite
javascript
vue
-
Vue 3 Sorting Tables by Column with TypeScript and Composition API
composition api
javascript
typescript
vue
-
Adding and Removing Items from a Multiple Select List with Vue.js
vue
Depending on your computer set-up, you might see this error when trying to start a Vite project without making any changes to the default template:
Error: listen EACCES: permission denied on 127.0.0.1:3000
For whatever reason, your computer isn't letting you use port 3000. Luckily, the fix is simple enough. You just need to add a line to the vite.config.ts
file to specify the port you want to use.
Inside the defineConfig
object, add the following line:
server: { port: 8080 }
Of course, you can change 8080 to be whatever port number you have access to. The complete file should look something like this:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
server: { port: 8080 }
})
Recently I migrated a Vue 3 project from Vue CLI to Vite. During the process, I took a bunch of notes. This project was using Javascript as opposed to TypeScript, so you might need to take that into account.
In my project, I will not be supporting older browsers, but Vite does have an official plugin for legacy browsers which you can find here:
https://github.com/vitejs/vite/tree/main/packages/plugin-legacy
package.json #
First things first, you want to remove the @vue/cli-service package from your project as well as any other Vue CLI plugins you might have. You can remove the @vue/cli-service package with this command:
npm un @vue/cli-service
In your package.json file, you also want to make sure you remove any dependencies that start with "@vue/cli-plugin". Here's an example of what was in mine:
"devDependencies": {
"@vue/cli-plugin-babel": "~4.5.0",
"@vue/cli-plugin-eslint": "~4.5.0",
"@vue/cli-plugin-router": "~4.5.0",
"@vue/cli-plugin-vuex": "~4.5.0",
// plus others
}
You can remove all of those at one with a command like this:
npm un @vue/cli-plugin-babel @vue/cli-plugin-eslint @vue/cli-plugin-router @vue/cli-plugin-vuex
Since I'm not supporting older browsers, I'm also removing babel:
npm un babel-eslint
Also, I need to delete the babel.config.js file.
Once all of those have been removed, add Vite and the @vitejs/plugin-vue as dev dependencies to your project.
npm i vite @vitejs/plugin-vue -D
Now, inside your package.json file, you will also want to add a "dev" script that you will use to start the local server for testing instead of using "serve" anymore as with Vue CLI. There will still be a "serve" script, but it is used to preview the production build locally, so instead of using "npm run serve" to test and develop, use "npm run dev" instead.
You will also want to change your build and lint scripts in that file as well. Just replacing vue-cli-service with vite should do the trick. Here's my old scripts section:
"scripts": {
"serve": "vue-cli-service serve",
"build-dev": "vue-cli-service build --mode development", // This is one I added
"build": "vue-cli-service build",
"lint": "vue-cli-service lint"
}
And my new one:
"scripts": {
"dev": "vite", // Use this to test locally
"serve": "vite preview",
"build-dev": "vite build --mode development", // This is one I added
"build": "vite build",
"lint": "vite lint"
}
index.html #
With Vue CLI, the index.html file is in public/index.html. You should move that file over to your project root directly instead.
You won't need to use the BASE_URL variable to find your favicon anymore, so replace:
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
With this:
<link rel="icon" href="/favicon.ico">
Vite will check your public folder for the favicon.
Vite does not auto-inject the main.js file into the index.html page, so you will need to add this line underneath the line:
<script type="module" src="/src/main.js"></script>
vite.config.js #
In the root folder, create the file vite.config.js. Inside, paste this code:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()]
})
If in your old vue.config.js file you have publicPath defined, you can add that right underneath the plugins line in your vite.config.js like this:
export default defineConfig({
plugins: [vue()],
base: '/YourBasePath/'
})
And if you need to have a different port number, you can also do so by adding this line under the plugins line:
server: { port: 8080 } // Or specify a different port number
If you've been using the @ alias in your paths, you will also want to add this under the plugins line:
resolve: {
alias: [
{
find: '@',
replacement: path.resolve(__dirname, 'src')
}
]
}
And to use the above, you will also need to import path like so:
import path from "path"
Once you have that all configured, you can delete the old vue.config.js file. Here's what my completed vite.config.js file looks like:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
base: '/NewBasePath/',
server: { port: 8080 },
resolve: {
alias: [
{
find: '@',
replacement: path.resolve(__dirname, 'src')
}
]
}
})
Environment Variables #
If you are using any environment variables (.env, .env.development, .env.production, etc.), you will want to change the VUE_APP_
prefix to VITE_
.
Whenever you need to use your environment variables in your code, you should now use import.meta.env.YOUR_VARIABLE_NAME
instead of process.env.YOUR_VARIABLE_NAME
.
For example, instead of using:
process.env.VUE_APP_TITLE
Use:
import.meta.env.VITE_TITLE
.vue Extensions #
One more thing you want to be aware of is when you are importing your components, you will now want to include the .vue extension, otherwise, Vite will not be able to find it. If you miss that step anywhere, you'll get an error in the console like this:
Uncaught (in promise) TypeError: Cannot destructure property ‘default' of ‘undefined' as it is undefined.
That's It #
Hopefully, this guide was able to help you out and you start enjoying how fast you can work with Vite is.
You can view the source code here:
https://github.com/kungfuphil/vue-sort-example
See it in action here:
First things first, this example uses Material Icons, so you’ll want to include this in your HTML:
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
For this example, I wanted to use a simple data set.
Here's what I came up with:
let data = [
{"id": 1, "name": "Phil"},
{"id": 2, "name": "Bobby"},
{"id": 3, "name": "Susie"},
];
Because this is TypeScript and we’re going to need it later, we also add an interface for this array.
interface dataInfo {
id: number,
name: string
}
Then we update the declaration of the data variable.
let data: Array<dataInfo> = [
{"id": 1, "name": "Phil"},
{"id": 2, "name": "Bobby"},
{"id": 3, "name": "Susie"},
];
Then we add a few more lines to allow us to access the data in the template.
const tableData = ref(data);
return {tableData};
The most simple table before we start adding the sorting logic looks like this.
<table>
<thead>
<tr>
<th>ID</th>
<th>Name</th>
</tr>
</thead>
<tbody>
<tr v-for="row in tableData" :key="row.id">
<td></td>
<td></td>
</tr>
</tbody>
</table>
Adding the sort icons is very simple. You can search and look through the Material icons here:
https://fonts.google.com/icons
<th>ID <span class="material-icons">sort</span></th>
In this example, I wanted to show one icon to show that sorting is possible for that column and another icon to show if we are sorting that column and in what direction. To do that, we need to include a few more variables.
const sortColumn = ref("id");
const sortDirection = ref(1);
By default, we will display the data sorted by ID in ascending order. You can change these values to suit your data’s needs. To make it a bit easier on ourselves, we will also include a variable to hold the name of the icon that shows the sort direction.
const arrowIconName = ref("arrow_drop_up");
Now to sort the data in different ways, we create this function to sort the data depending on what column we select.
const sortByColumn = (columnName: keyof dataInfo) => {
sortColumn.value = columnName;
sortDirection.value = -1 * sortDirection.value;
if (sortDirection.value == 1) {
arrowIconName.value = "arrow_drop_up";
tableData.value.sort((a, b) => (a[columnName] > b[columnName] ? 1 : -1));
} else {
arrowIconName.value = "arrow_drop_down";
tableData.value.sort((a, b) => (a[columnName] < b[columnName] ? 1 : -1));
}
}
Finally, we give the column headers a click event and tie that to the function above to do the sorting. We also need to update the icons to show whether each column is being sorted or not as well as show the direction of the sort.
<th @click="sortByColumn('id')">ID
<span v-if="sortColumn == 'id'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
<th @click="sortByColumn('name')">Name
<span v-if="sortColumn == 'name'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
Finally here’s what the entire component looks like:
<template>
<table>
<thead>
<tr>
<th @click="sortByColumn('id')">ID
<span v-if="sortColumn == 'id'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
<th @click="sortByColumn('name')">Name
<span v-if="sortColumn == 'name'" class="material-icons"></span>
<span v-else class="material-icons">sort</span>
</th>
</tr>
</thead>
<tbody>
<tr v-for="row in tableData" :key="row.id">
<td></td>
<td></td>
</tr>
</tbody>
</table>
</template>
<script lang="ts">
import { defineComponent, ref } from 'vue'
export default defineComponent({
setup() {
interface dataInfo {
id: number,
name: string
}
let data: Array<dataInfo> = [
{"id": 1, "name": "Phil"},
{"id": 2, "name": "Bobby"},
{"id": 3, "name": "Susie"},
];
const tableData = ref(data);
const sortColumn = ref("id");
const sortDirection = ref(1);
const arrowIconName = ref("arrow_drop_up");
const sortByColumn = (columnName: keyof dataInfo) => {
sortColumn.value = columnName;
sortDirection.value = -1 * sortDirection.value;
if (sortDirection.value == 1) {
arrowIconName.value = "arrow_drop_up";
tableData.value.sort((a, b) => (a[columnName] > b[columnName] ? 1 : -1));
} else {
arrowIconName.value = "arrow_drop_down";
tableData.value.sort((a, b) => (a[columnName] < b[columnName] ? 1 : -1));
}
}
return {tableData, sortColumn, sortDirection, arrowIconName, sortByColumn};
},
})
</script>
<style scoped>
table {
border-collapse: collapse;
margin: auto;
box-shadow: 0 0 20px rgba(0, 0, 0, 0.15);
}
thead tr {
background-color: gray;
color: white;
}
tbody tr {
border-bottom: 1px solid black;
}
th, td {
padding: 12px 15px;
}
.material-icons {
vertical-align: -6px;
}
th {
cursor: pointer;
}
</style>
First, we create the html page. I just named this one index.html. Over in line 7, we are including the Vue.js framework in our page. In the body, we have a div with the class "vue-app" which we will define later in a file called app.js where we will keep our Vue app code.
In line 12, we create an input box and bind it to "listItem". This array will hold all the items we enter. Right underneath that, we add a button which will call the addToList() function in app.js when it is clicked.
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<link href="styles.css" rel="stylesheet" />
<script src="https://cdn.jsdelivr.net/npm/vue@2.6.0"></script>
</head>
<body>
<div id="vue-app">
<input type="text" v-model="listItem">
<button v-on:click="addToList()">Enter</button>
<select multiple>
<option v-for="(item, index) in list" value=""
v-on:dblclick="removeFromList(index)">
</option>
</select>
</div>
<script src="app.js"></script>
</body>
</html>
select {
display: block;
}
new Vue({
el: '#vue-app',
data: {
listItem: '',
list: []
},
methods: {
addToList: function() {
console.log(`Adding ${this.listItem} to list`);
this.list.push(this.listItem);
this.listItem = '';
},
removeFromList: function(index) {
console.log(`Removing element from index ${index}`);
this.list.splice(index, 1);
}
}
});
See all tags.