diff --git a/docs/plugin-dev-ui.md b/docs/plugin-dev-ui.md index 6701a8661..c574272d6 100644 --- a/docs/plugin-dev-ui.md +++ b/docs/plugin-dev-ui.md @@ -44,6 +44,10 @@ You should add the url to the plugin website or repository in the `homepage` or } ``` +

+ +

+ ## UI API The cli-ui exposes an API that allows augmenting the project configurations and tasks, as well as sharing data and communicating with other processes. @@ -62,17 +66,430 @@ module.exports = api => { ### Project configurations +You can add a project configuration with the `api.describeConfig` method. + +First you need to pass some informations: + +```js +api.describeConfig({ + // Unique ID for the config + id: 'eslintrc', + // Displayed name + name: 'ESLint configuration', + // Shown below the name + description: 'Error checking & Code quality', + // "More info" link + link: 'https://eslint.org' +}) +``` + +Specify an icon with either a file type (like `'json'`) or a file name (like `.babelrc` to get the babel icon). This is powered by file-icons. + +```js +api.describeConfig({ + /* ... */ + // Icon generated using file-icons + icon: '.eslintrc.json' +}) +``` + +Then you can specify which files will be read when loading the configuration and then written to (JS files aren't supported yet): + +```js +api.describeConfig({ + /* ... */ + // All possible files for this config + files: { + json: ['.eslintrc', '.eslintrc.json'], + // Will read from `package.json` + package: 'eslintConfig' + }, +}) +``` + +Supported types: `json`, `yaml`, `package`. + +Use the `onRead` hook to return a list of prompts to be displayed for the configuration: + +```js +api.describeConfig({ + /* ... */ + onRead: ({ data }) => ({ + prompts: [ + // Prompt objects + ] + }) +}) +``` + +The prompt objects must be valid [inquirer](https://github.com/SBoudrias/Inquirer.js) prompts with the following additional fields (which are optional): + +```js +{ + /* ... */ + // Used to group the prompts into sections + group: 'Strongly recommended', + // Additional description + description: 'Enforce attribute naming style in template (`my-prop` or `myProp`)', + // "More info" link + link: 'https://github.com/vuejs/eslint-plugin-vue/blob/master/docs/rules/attribute-hyphenation.md', +} +``` + +Supported inquirer types: `checkbox`, `confirm`, `input`, `password`, `list`, `rawlist`. + +Use the `onWrite` hook to write the data to the configuration file (or execute any node code): + +```js +api.describeConfig({ + /* ... */ + onWrite: ({ prompts, answers, data, file, api }) => { + // ... + } +}) +``` + +Arguments: + +- `prompts`: current prompts runtime objects +- `answers`: answers data from the user inputs +- `data`: initial data read from the file +- `file`: descriptor of the found file (`{ type: 'json', path: '...' }`) +- `api`: onWrite API + +Prompts runtime objects: + +```js +{ + id: data.name, + type: data.type, + name: data.short || null, + message: data.message, + group: data.group || null, + description: data.description || null, + link: data.link || null, + choices: null, + visible: true, + enabled: true, + // Current value (not filtered) + value: null, + // true if changed by user + valueChanged: false, + error: null, + // Original inquirer prompt object + raw: data +} +``` + +onWrite API: + +- `assignData(newData)`: use `Object.assign` to update the config data before writing. +- `setData(newData)`: each key of `newData` will be deeply set (or removed if `undefined` value) to the config data before writing. +- `async getAnswer(id, mapper)`: retrieve answer for a given prompt id and map it through `mapper` function if provide (for example `JSON.parse`). + ### Project tasks +Tasks are generated from the `scripts` field in the project `package.json` file. + +You can 'augment' the tasks with additional info and hooks thanks to the `api.describeTask` method: + +```js +api.describeTask({ + // RegExp executed on script commands to select which task will be described here + match: /vue-cli-service serve/, + description: 'Compiles and hot-reloads for development', + // "More info" link + link: 'https://github.com/vuejs/vue-cli/blob/dev/docs/cli-service.md#serve', + // Optional parameters (inquirer prompts) + prompts: [ + { + name: 'open', + type: 'confirm', + default: false, + description: 'Open browser on server start' + }, + { + name: 'mode', + type: 'list', + default: 'development', + choices: [ + { + name: 'development', + value: 'development' + }, + { + name: 'production', + value: 'production' + }, + { + name: 'test', + value: 'test' + } + ], + description: 'Specify env mode' + } + ], + // Hooks + // Modify arguments here + onBeforeRun: ({ answers, args }) => { + // Args + if (answers.open) args.push('--open') + if (answers.mode) args.push('--mode', answers.mode) + args.push('--dashboard') + }, + // Immediatly after running the task + onRun: ({ args, child, cwd }) => { + // child: node child process + // cwd: process working directory + }, + onExit: ({ args, child, cwd, code, signal }) => { + // code: exit code + // signal: kill signal used if any + }, + // Additional views (for example the webpack dashboard) + // By default, there is the 'output' view which displays the terminal output + views: [ + { + // Unique ID + id: 'vue-webpack-dashboard-client-addon', + // Button label + label: 'Dashboard', + // Button icon + icon: 'dashboard', + // Dynamic component to load (see 'Client addon' section below) + component: 'vue-webpack-dashboard' + } + ], + // Default selected view when displaying the task details (by default it's the output) + defaultView: 'vue-webpack-dashboard' +}) +``` + ### Client addon +A Client addon is a JS bundle which is dynamically loaded into the cli-ui. It is useful to load custom components and routes. + +#### Create a client addon + +The recommended way to create a Client addon is by creating a new project using vue-cli 3. You can either do this in a subfolder of your plugin or in a different npm package. + +Then add a `vue.config.js` file with the following content: + +```js +module.exports = { + // Change the id here + baseUrl: '/_addon/', + // You can change the port here + devBaseUrl: 'http://localhost:8042/', + configureWebpack: { + output: { + // Important + filename: 'index.js' + } + }, + // Don't extract CSS into a separate file + css: { + extract: false + }, + // Remove unneeded webpack plugins + chainWebpack: config => { + config.plugins.delete('preload') + config.plugins.delete('prefetch') + config.plugins.delete('html') + config.plugins.delete('split-vendor') + config.plugins.delete('split-vendor-async') + config.plugins.delete('split-manifest') + }, + // Configure dev server + devServer: { + headers: { + 'Access-Control-Allow-Origin': '*' + }, + // You can change the port here + port: 8042 + } +} +``` + +**Don't forget to replace `` in the `baseUrl` field with the id of your new client addon!** + +Then modify the `.eslintrc.json` file to add some allowed global objects: + +```json +{ + // ... + "globals": { + "ClientAddonApi": false, + "mapSharedData": false, + "Vue": false + } +} +``` + +You can now run the `serve` script in development and the `build` one when you are ready to publish your plugin. + +#### ClientAddonApi + +Open the `main.js` file in the client addon sources and remove all the code. + +**Don't import Vue in the client addon sources, use the global `Vue` object from the browser `window`.** + +Here is an example of code for `main.js`: + +```js +import VueProgress from 'vue-progress-path' +import WebpackDashboard from './components/WebpackDashboard.vue' +import TestView from './components/TestView.vue' + +// You can install additional vue plugins +// using the global 'Vue' variable +Vue.use(VueProgress, { + defaultShape: 'circle' +}) + +// Register a custom component +// (works like 'Vue.component') +ClientAddonApi.component('vue-webpack-dashboard', WebpackDashboard) + +// Add routes to vue-router under a /addon/ parent route. +// For example, addRoutes('foo', [ { path: '' }, { path: 'bar' } ]) +// will add the /addon/foo/ and the /addon/foo/bar routes to vue-router. +// Here we create a new '/addon/vue-webpack/' route with the 'test-webpack-route' name +ClientAddonApi.addRoutes('vue-webpack', [ + { path: '', name: 'test-webpack-route', component: TestView } +]) +``` + +The cli-ui registers `Vue` and `ClientAddonApi` as global variable in the `window` scope. + +#### Register the client addon + +**Back to the UI API (in the `ui.js` file).** + +Use the `api.addClientAddon` method with a require query to the built folder: + +```js +api.addClientAddon({ + id: 'vue-webpack', + // Folder containing the built JS files + path: '@vue/cli-ui-addon-webpack/dist' +}) +``` + +Or specify an url when developping the plugin (ideally you want to do this in the `vue-cli-ui.js` file in your test vue project): + +```js +// Useful for dev +// Will override path if already defined in a plugin +api.addClientAddon({ + id: 'vue-webpack', + // Use the same port you configured earlier + url: 'http://localhost:8042/index.js' +}) +``` + +#### Use the client addon + +You can now use the client addon in the views. For example, you can specify a view in a described task: + +```js +api.describeTask({ + /* ... */ + // Additional views (for example the webpack dashboard) + // By default, there is the 'output' view which displays the terminal output + views: [ + { + // Unique ID + id: 'vue-webpack-dashboard-client-addon', + // Button label + label: 'Dashboard', + // Button icon (material-icons) + icon: 'dashboard', + // Dynamic component to load, registered using ClientAddonApi + component: 'vue-webpack-dashboard' + } + ], + // Default selected view when displaying the task details (by default it's the output) + defaultView: 'vue-webpack-dashboard' +}) +``` + +### Custom views + +You can add a new view below the standard 'Project plugins', 'Project configuration' and 'Project tasks' ones using the `api.addView` method: + +```js +api.addView({ + // Unique id + id: 'vue-webpack-test-view', + + // Route name (from vue-router) + // Use the same name used in the 'ClientAddonApi.addRoutes' method (see above in the Client addon section) + name: 'test-webpack-route', + + // Button icon (material-icons) + icon: 'pets', + // You can also specify a custom image (see Public static files section below): + // icon: 'http://localhost:4000/_plugin/%40vue%2Fcli-service/webpack-icon.svg', + + // Button tooltip + tooltip: 'Test view from webpack addon' +}) +``` + ### Shared data +Use Shared data to communicate info with custom components in an easy way. + +In the plugin `ui.js`: + +```js +// Set or update +api.setSharedData('my-variable', 'some-data') +// Get +api.getSharedData('my-variable') +// Namespaced versions +const { setSharedData, getSharedData } = api.namespace('webpack-dashboard-') +``` + +In the custom component: + +```js +{ + // Sync Shared data + sharedData () { + return { + // You can use `status` in template + status: `webpack-dashboard-${this.mode}-status` + // You can also map namespaced Shared data + ...mapSharedData('webpack-dashboard-', { + status: `${this.mode}-status`, + progress: `${this.mode}-progress`, + operations: `${this.mode}-operations` + }) + } + }, + + // Manual methods + async created () { + const value = await this.$getSharedData('my-variable') + + this.$watchSharedData(`my-variable`, value => { + console.log(value) + }) + + await this.$setSharedData('my-variable', 'new-value') + } +} +``` + ### Plugin actions +TODO + ### IPC -### Custom views +TODO ### Public static files diff --git a/docs/plugin-search-item.png b/docs/plugin-search-item.png new file mode 100644 index 000000000..004ec10da Binary files /dev/null and b/docs/plugin-search-item.png differ diff --git a/packages/@vue/cli-service/ui.js b/packages/@vue/cli-service/ui.js index 77540d969..2590acdcb 100644 --- a/packages/@vue/cli-service/ui.js +++ b/packages/@vue/cli-service/ui.js @@ -197,7 +197,7 @@ module.exports = api => { defaultView: 'vue-webpack-dashboard' }) - // Testing client addon + // Webpack dashboard api.addClientAddon({ id: 'vue-webpack', path: '@vue/cli-ui-addon-webpack/dist'