I want to write a Plugin for Vue.js 3, and I intend to write it with TypeScript. Normally, this is relative easy, there are plenty of tutorials and article about this topic. But one part was always missing: A plugin with custom Single File Components and also written in TypeScript.
This article consists of two parts:
- Writing the Plugin – you are here
- Installing the Plugin
Inhaltsverzeichnis
Write the plugin
Writing a new plugin consists of the following few steps
- Initialize a new project
- Write the actual Plugin
- Configure TypeScript
- Configure rollup.js
Initialize a the project
mkdir my-plugin
cd my-plugin
yarn init -y
Now we have a new, but empty project. Let’s add a few packages to our devDependencies
- Vue So we can create our custom components in our plugin
- TypeScript (Will compile our *.ts and *.vue files to vanilla JavaScript)
- rollup.js with plugins for Vue & TypeScript (“Rollup is a module bundler for JavaScript which compiles small pieces of code into something larger and more complex, such as a library or application.” – From the official documentation at rollupjs.org) See it as an alternative to Webpack.
yarn add -D vue typescript rollup rollup-plugin-typescript2 rollup-plugin-vue rollup-plugin-clear
Now is a good moment, to initialize a new git repository. This step is optional, but highly recommended
git init
echo "node_modules" > .gitignore
git add .
git commit -m"init"
Write the plugin
Our plugin will be simple. It consists of a single component with a static text (Hello World) and with a configurable text color. The color can be set while installing the plugin in the example project.
The Plugin will be registered globally as this.$coloredText
Our Single File Component (SFC)
<template>
<h1 :style="{ color: $coloredText.textColor }">
<slot></slot>
</h1>
</template>
<script lang="ts">
import { defineComponent } from "vue";
export default defineComponent({
name: "Headline"
})
</script>
The definition of our Plugin
This is the definition of the actual plugin, used later in your Vue.js app.
export interface ColoredText
{
textColor: string
}
The definition of our Plugin Options
This will define all the options of your plugin.
export interface ColoredTextOptions {
color: string
}
The Plugin
This file contains the install
function, needed by Vue.js to register the plugin
import { App, Plugin } from 'vue';
import Headline from './components/Headline.vue'
import { ColoredText } from "./types/ColoredText";
import { ColoredTextOptions } from './types/ColoredTextOptions';
// Used to create a new ColoredText. "options" will be whatever you add later to "app.use(ColoredTextPlugin, options);"
const createColoredText = (options: ColoredTextOptions): ColoredText => {
return {
textColor: options.color,
}
}
// The Install function used by Vue to register the plugin
export const ColoredTextPlugin: Plugin = {
install(app: App, options: ColoredTextOptions) {
// makes ColoredText available in your Vue.js app as either "$this.coloredText" (in your Source) or "{{ $coloredText }}" in your templates
app.config.globalProperties.$coloredText = createColoredText(options)
// register Headline as a global component, so you can use it wherever you want in your app
app.component('Headline', Headline)
}
}
Let’s combine everything together. This will be the entry point to our package
import Headline from './components/Headline.vue'
import { ColoredTextOptions } from './types/ColoredTextOptions';
import { ColoredText } from './types/ColoredText';
import { ColoredTextPlugin} from './ColoredTextPlugin'
export { Headline, ColoredText, ColoredTextOptions, ColoredTextPlugin }
Define build process
Configure TypeScript
We want our plugin to be compiled to esnext
, our output directory should be ./dist
and all *.ts
and *.vue
Disclaimer: I am by no means a TypeScript expert, so let me please know, if I missed a crucial configuration, but this works for me
{
"compilerOptions": {
/* Set the JavaScript language version for emitted JavaScript and include
compatible library declarations. */
"target": "esnext",
/* Specify what JSX code is generated. */
"jsx": "preserve",
/* Specify what module code is generated. */
"module": "esnext",
/* Specify the root folder within your source files. */
"rootDir": "./src",
/* Specify how TypeScript looks up a file from a given module specifier. */
"moduleResolution": "node",
/* Specify the base directory to resolve non-relative module names. */
"baseUrl": "./src",
/* Generate .d.ts files from TypeScript and JavaScript files in your project. */
"declaration": true,
/* Create sourcemaps for d.ts files. */
"declarationMap": true,
/* Create source map files for emitted JavaScript files. */
"sourceMap": true,
/* Specify an output folder for all emitted files. */
"outDir": "./dist",
/* Emit additional JavaScript to ease support for importing CommonJS modules.
This enables `allowSyntheticDefaultImports` for type compatibility. */
"esModuleInterop": true,
/* Ensure that casing is correct in imports. */
"forceConsistentCasingInFileNames": true,
/* Enable all strict type-checking options. */
"strict": true,
/* Skip type checking all .d.ts files. */
"skipLibCheck": true
},
"include": [
"src/**/*.ts",
"src/**/*.d.ts",
"src/**/*.tsx",
"src/**/*.vue"
],
"exclude": [
"**/node_modules",
]
}
Now comes one important step. We must tell TypeScript how to handle *.vue
files
declare module '*.vue' {
import {defineComponent} from 'vue';
const component: ReturnType<typeof defineComponent>;
export default component;
}
Configure rollup.js
Now we must configure rollup.js. This will compile my *.ts
files with TypeScript, wraps everything together to a nice compact bundle and saves it to ./dist
import typescript from 'rollup-plugin-typescript2';
import vue from 'rollup-plugin-vue';
import clear from 'rollup-plugin-clear';
export default async function config(args) {
return {
input: 'src/index.ts',
output: {
dir: 'dist',
format: 'cjs',
sourcemap: true,
},
plugins: [
vue(),
typescript({
tsconfigOverride: {
compilerOptions: {
declaration: true,
},
include: null,
},
}),
clear({
targets: ['./dist'],
})
],
};
}
Lastly, we must add the path to our compiled src/index.ts
and our build scripts to our package.json
. Don’t forget to change @your-npm-scope
to your actual scope. For details, see here.
{
"name": "@skuhnow/vue3-plugin-typescript",
"version": "1.0.6",
"main": "dist/index.js",
"license": "MIT",
"peerDependencies": {
"vue": "^3.2.20"
},
"devDependencies": {
"rollup": "^2.58.0",
"rollup-plugin-clear": "^2.0.7",
"rollup-plugin-typescript2": "^0.30.0",
"rollup-plugin-vue": "^6.0.0",
"typescript": "^4.4.4"
},
"scripts": {
"build": "rollup -c",
"prepublishOnly": "yarn build"
}
}
Publish to npm
Now we are ready to publish our glorious package to the npm Registry. I assume, you are already registered, if not, it’s not that hard: Creating and publishing scoped public packages
Don’t forget to increase your package version before publishing
To publish just write
npm publish --access public
Thanks
I have a question, how to resolve this issue. That’s happens because I added to my component
[!] (plugin rpt2) RollupError: Unexpected token (Note that you need plugins to import files that are not JavaScript)