Skip to content

Instantly share code, notes, and snippets.

@hoshiyosan
Last active July 22, 2024 00:43
Show Gist options
  • Save hoshiyosan/ad3a27257cfff07286f4a83a81fe841b to your computer and use it in GitHub Desktop.
Save hoshiyosan/ad3a27257cfff07286f4a83a81fe841b to your computer and use it in GitHub Desktop.
Create a Vue.js plugin using Typescript

Create a Vue.js plugin using Typescript

Initialize project with npm

Create the project directory and initialize an npm project

mkdir my-plugin && cd my-plugin
npm init -y

Install vue

npm i --save vue

Install dependencies required by webpack to build typescript and .vue files.

npm i --save-dev typescript webpack ts-loader css-loader vue-loader vue-template-compiler

Optionally (recommended) if you want to use vue class-based components syntax

vue-class-component vue-property-decorator

Configure typescript

Create the following tsconfig.json

{
    "compilerOptions": {
        "outDir": "./dist/",
        "sourceMap": true,
        "strict": true,
        "noImplicitReturns": true,      
        "experimentalDecorators": true,
        "allowSyntheticDefaultImports": true,
        "noImplicitAny": false,
        "module": "es2015",
        "declaration": true,
        "moduleResolution": "node",
        "target": "es5",
        "lib": [
            "dom",
            "es5",
            "es2015.promise"
        ]
    },
    "include": [
        "./src/**/*"
    ]
}

Configure webpack

Créer un fichier de config webpack.config.js

var path = require('path')
var webpack = require('webpack')
const { VueLoaderPlugin } = require('vue-loader')
 
module.exports = {
  entry: './src/index.ts',
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'index.js',
    libraryTarget: 'umd',
    libraryExport: 'default' //<-- make export accessible to global scope
  },
  module: {
    rules: [
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            'scss': 'vue-style-loader!css-loader!sass-loader',
            'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax',
          }
        }
      },
      {
        test: /\.tsx?$/,
        loader: 'ts-loader',
        exclude: /node_modules/,
        options: {
          appendTsSuffixTo: [/\.vue$/],
        }
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  resolve: {
    extensions: ['.ts', '.js', '.vue', '.json'],
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    }
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true
  },
  performance: {
    hints: false
  },
  plugins: [
    // make sure to include the plugin for the magic
    new VueLoaderPlugin()
  ]
}

Minimalist plugin content

Index of the project

At the very least Vue expect your plugin to have an install() method, in which you can register components or add properties to Vue instances.

See Vue.js / typescript official documentation, including how to structure plugin

With the current webpack configuration, your project must contains an src/index.ts file, that export your plugin's declarations.

import { VueConstructor } from 'vue/types/umd'
import HelloTarget from './components/HelloTarget.vue'

export default {
    /**
     * Function that will be used by Vue to install your plugin.
     * It is called when you do Vue.use(MyPlugin)
     */
    install(Vue: VueConstructor, options?: {target?: string}) {
        // add custom property $target to all vue instances
        Vue.prototype.$target = (options && options.target) || "everyone"

        // register your first component
        Vue.component('hello-world', HelloTarget)
    }
}

Augment Vue by redeclaring it ???

Create the files typings/vue/index.d.ts

// Augment Vue with custom property $target
// https://stackoverflow.com/questions/50909703/augmenting-vue-js-in-typescript
declare module 'vue/types/vue' {
    interface Vue { 
        $target: string
    }
}

Your linter might complains that it doesn't know how to process .vue files.

To solve this issue, create a src/vue-shim.d.ts with the following content

declare module "*.vue" {
  import Vue from 'vue'
  export default Vue
}

First template example

Create a HelloTarget template in src/components/HelloTarget.vue, that uses the custom property created prevously...

<template>
  <h1>{{ message }}</h1>
</template>

<script lang="ts">
import Vue from "vue";

export default Vue.extend({
  computed: {
    message() {
      return `Hello ${this.$target}!`;
    },
  },
});
</script>

Customize package.json

Set apropriate values for your package in package.json, and add the following fields to it.

{
  "main": "dist/index.js",
  "types": "dist/index.d.ts",

  "scripts": {
    "build": "webpack",
    "watch": "webpack --watch"
  }
}

Build your project

npm run build

Local test of the library

Create a vue project and switch to it.

vue create my-project && cd my-project

Then install your package from local filesystem.

npm install ../path/to/my-plugin

and add the followings rows in main.ts / main.js (to keep it simple).

import Vue from 'vue'
import MyPlugin from 'my-plugin'

Vue.use(MyPlugin)
// or
Vue.use(MyPlugin, {target: 'world'})

Then in any .vue template, you can use your plugin.

For example, edit the default App.vue like this.

<template>
  <hello-target/>
</template>

And run

npm run serve

Possible issues

  • No ESLint configuration found

In my-project, create a .eslintignore file containing

../path/to/my-plugin

The issue is due to the fact that installing local package using npm result in all file being copied, whereas these files doesn't exists when your package is hosted on npm.

Sources

Base documentation

Start a project with vue/typescript/webpack

Vue typescript official documentation, include create plugin

Issues and solutions

VueLoaderPlugin

vue-loader was used without the corresponding plugin. Make sure to include VueLoaderPlugin in your webpack config.

symfony/webpack-encore#311

Solution

The vue-loader plugin is not automatically used by webpack. In order to load it, add this section to webpack.config.js

const { VueLoaderPlugin } = require('vue-loader')

{
    plugins: [
        // make sure to include the plugin for the magic
        new VueLoaderPlugin()
    ]
}

Generate typing on webpack build

When running webpack --reload, typing files (.d.ts) are not generated in the build.

https://stackoverflow.com/questions/34786199/using-webpack-to-generate-typescript-libraries-with-typing-files

Solution

Add the "declaration": true to tsconfig.json

{
    "compilerOptions": {
        "declaration": true,
        ...
}

Add custom properties into vue instance

https://vuejs.org/v2/cookbook/adding-instance-properties.html

Include typing for library to be imported in typescript

Solution

Add the types field to package.json

    "main": "dist/index.js",
    "types": "dist/index.d.ts"

Webpack make export accessible to global scope

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment