├── .commitlintrc ├── .editorconfig ├── .eslintignore ├── .eslintrc ├── .github ├── FUNDING.yml └── workflows │ └── nodejs.yml ├── .gitignore ├── .huskyrc.js ├── .lintstagedrc.js ├── .prettierrc ├── .remarkrc ├── .renovaterc ├── .versionrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── codechecks.yml ├── docs └── index.html ├── package.json ├── scripts └── deploy.sh ├── src ├── dynamic.ts ├── index.ts └── utils.ts ├── tsconfig.json ├── typings.d.ts └── yarn.lock /.commitlintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "@1stg" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | indent_size = 2 6 | tab_width = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | lib 2 | !/.*.js 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@1stg" 3 | } 4 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: [JounQin] 2 | open_collective: rxts 3 | -------------------------------------------------------------------------------- /.github/workflows/nodejs.yml: -------------------------------------------------------------------------------- 1 | name: Node CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | default: 7 | strategy: 8 | matrix: 9 | node: [12] 10 | os: [macOS-latest, ubuntu-latest] 11 | runs-on: ${{ matrix.os }} 12 | steps: 13 | - uses: actions/checkout@master 14 | 15 | - uses: actions/setup-node@master 16 | with: 17 | node-version: ${{ matrix.node }} 18 | 19 | - name: Setup yarn 20 | run: | 21 | curl -o- -L https://yarnpkg.com/install.sh | bash 22 | export PATH="$HOME/.yarn/bin:$PATH" 23 | 24 | - name: Get yarn cache 25 | id: yarn-cache 26 | run: echo "::set-output name=dir::$(yarn cache dir)" 27 | 28 | - uses: actions/cache@v1 29 | with: 30 | path: ${{ steps.yarn-cache.outputs.dir }} 31 | key: ${{ matrix.os }}-${{ matrix.node }}-yarn-${{ hashFiles('**/yarn.lock') }} 32 | restore-keys: | 33 | ${{ matrix.os }}-${{ matrix.node }}-yarn- 34 | 35 | - name: Install Dependencies 36 | run: yarn --frozen-lockfile 37 | env: 38 | CI: 'true' 39 | 40 | - name: Build, Lint and Test 41 | run: | 42 | yarn build 43 | yarn lint 44 | env: 45 | EFF_NO_LINK_RULES: 'true' 46 | PARSER_NO_WATCH: 'true' 47 | 48 | # - name: Codecov 49 | # if: matrix.os == 'macOS-latest' 50 | # run: | 51 | # yarn global add codecov codacy-coverage 52 | # codecov 53 | # cat ./coverage/lcov.info | codacy-coverage -u JounQin -n vue-dynamic 54 | # env: 55 | # CI: 'true' 56 | # CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} 57 | # CODACY_ACCOUNT_TOKEN: ${{ secrets.CODACY_ACCOUNT_TOKEN }} 58 | # CODACY_PROJECT_TOKEN: ${{ secrets.CODACY_PROJECT_TOKEN }} 59 | 60 | - name: Code Checks 61 | if: matrix.os == 'macOS-latest' && github.event_name == 'push' 62 | run: | 63 | yarn global add @codechecks/client @codechecks/build-size-watcher 64 | codechecks 65 | env: 66 | CI: 'true' 67 | CC_SECRET: ${{ secrets.CC_SECRET }} 68 | 69 | - name: Publish GitHub Release and npm Package 70 | if: matrix.os == 'macOS-latest' && github.event_name == 'push' && github.ref == 'refs/heads/master' 71 | run: | 72 | git remote set-url origin "https://$GITHUB_ACTOR:$GH_TOKEN@github.com/$GITHUB_REPOSITORY.git" 73 | npm set //registry.npmjs.org/:_authToken $NPM_TOKEN 74 | git checkout master 75 | bash scripts/deploy.sh 76 | env: 77 | CI: 'true' 78 | GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} 79 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 80 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .*cache 2 | *.log 3 | lib 4 | node_modules 5 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@1stg/husky-config') 2 | -------------------------------------------------------------------------------- /.lintstagedrc.js: -------------------------------------------------------------------------------- 1 | module.exports = require('@1stg/lint-staged') 2 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | "@1stg/prettier-config/vue" 2 | -------------------------------------------------------------------------------- /.remarkrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "@1stg/remark-config" 4 | ] 5 | } 6 | -------------------------------------------------------------------------------- /.renovaterc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@1stg" 3 | } 4 | -------------------------------------------------------------------------------- /.versionrc: -------------------------------------------------------------------------------- 1 | { 2 | "skip": { 3 | "bump": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## 0.4.0 (2020-02-08) 6 | 7 | ### Features 8 | 9 | - Custom Events on `dynamic` component is supported, see [#23](https://github.com/JounQin/vue-dynamic/issues/23) for more details ([1450f15](https://github.com/JounQin/vue-dynamic/commit/1450f153dab91c67ecd32e865e21587fc488efcf)) 10 | - migrate to TypeScript ([29ad055](https://github.com/JounQin/vue-dynamic/commit/29ad0553c5aef084e70f192ac14106d0daa00db8)) 11 | 12 | ### Bug Fixes 13 | 14 | - transform to es5 compatible ES Module files, close [#25](https://github.com/JounQin/vue-dynamic/issues/25) ([3a8d362](https://github.com/JounQin/vue-dynamic/commit/3a8d3626ba80233741cd7157683adb3181f08069)) 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016-present JounQin 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-dynamic 2 | 3 | [![GitHub Actions](https://github.com/JounQin/vue-dynamic/workflows/Node%20CI/badge.svg)](https://github.com/JounQin/vue-dynamic/actions?query=workflow%3A%22Node+CI%22) 4 | [![Codacy Grade](https://img.shields.io/codacy/grade/a8dfdfc423974b6e83f81215aee07e7e)](https://www.codacy.com/gh/JounQin/vue-dynamic) 5 | [![npm](https://img.shields.io/npm/v/vue-dynamic.svg)](https://www.npmjs.com/package/vue-dynamic) 6 | [![GitHub Release](https://img.shields.io/github/release/JounQin/vue-dynamic)](https://github.com/JounQin/vue-dynamic/releases) 7 | 8 | [![David Peer](https://img.shields.io/david/peer/JounQin/vue-dynamic.svg)](https://david-dm.org/JounQin/vue-dynamic?type=peer) 9 | [![David](https://img.shields.io/david/JounQin/vue-dynamic.svg)](https://david-dm.org/JounQin/vue-dynamic) 10 | [![David Dev](https://img.shields.io/david/dev/JounQin/vue-dynamic.svg)](https://david-dm.org/JounQin/vue-dynamic?type=dev) 11 | 12 | [![Conventional Commits](https://img.shields.io/badge/conventional%20commits-1.0.0-yellow.svg)](https://conventionalcommits.org) 13 | [![Renovate enabled](https://img.shields.io/badge/renovate-enabled-brightgreen.svg)](https://renovatebot.com) 14 | [![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com) 15 | [![Code Style: Prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 16 | [![codechecks.io](https://raw.githubusercontent.com/codechecks/docs/master/images/badges/badge-default.svg?sanitize=true)](https://codechecks.io) 17 | 18 | Load stringified or normal Vue components dynamically! 19 | 20 | ## TOC 21 | 22 | - [Notice](#notice) 23 | - [Usage](#usage) 24 | - [Changelog](#changelog) 25 | - [License](#license) 26 | 27 | ## Notice 28 | 29 | This module is just a simple wrapper of Vue's built-in `component`, and you should only use it to use stringified static components. 30 | 31 | ## Usage 32 | 33 | _1.Global Component_ 34 | 35 | ```js 36 | import Vue from 'vue' // make sure to use 'vue/dist/vue.js' because we will use template 37 | import VueDynamic from 'vue-dynamic' 38 | 39 | Vue.use(VueDynamic, { name: 'dynamic' }) // you can custom the global component name and it's default name is 'dynamic' 40 | ``` 41 | 42 | Then it will be same with the next case: 43 | 44 | _2.Specific Component_ 45 | 46 | ```vue 47 | 50 | 67 | ``` 68 | 69 | It needs you to pass two props to `Dynamic`, `emptyView` is required because it will be used when we failed to pass your `comps`. 70 | 71 | `comps` can be a Object like the normal `components` option in `*.vue` file of an Array of Vue-Component-like Object. 72 | 73 | There is a deadly simple example: 74 | 75 | ```js 76 | ;[ 77 | { 78 | template: `
{{ msg }}
`, 79 | data: { 80 | msg: `It's the first dynamic template!`, 81 | }, 82 | }, 83 | { 84 | template: `
{{ reverse ? $options.filters.reverse(msg) : msg }} 85 |
`, 86 | data: { 87 | msg: `It's the second dynamic template!`, 88 | reverse: false, 89 | }, 90 | methods: { 91 | reverseMsg: 'this.reverse = !this.reverse', 92 | }, 93 | }, 94 | { 95 | template: `
More Magic Here!
`, 96 | }, 97 | ] 98 | ``` 99 | 100 | As you see, the value of `methods` object is a string (or array, them will be applied to `Function` constructor) what means you can store it in your database! So that it is possible to define customer defined page component separately and link them together at once! 101 | 102 | It's very useful to build a html5 page like [eqxiu.com](http://www.eqxiu.com/). 103 | 104 | **_And nested components can be used! Here is a example:_** 105 | 106 | ```js 107 | ;[ 108 | { 109 | template: '
', 110 | components: { 111 | component1: { 112 | template: '
{{ msg }}
', 113 | data: { 114 | msg: 'Inner Message', 115 | }, 116 | methods: { 117 | click: 'alert("abc")', 118 | }, 119 | }, 120 | component2: { 121 | template: '
{{ msg }}
', 122 | data: { 123 | msg: 'Inn1222er Message', 124 | }, 125 | methods: { 126 | click: 'alert("ab11c")', 127 | }, 128 | components: { 129 | component3: { 130 | template: ``, 131 | data: { 132 | msg: `I'm the third one!`, 133 | }, 134 | methods: { 135 | reverse: `this.msg = this.msg.split('').reverse().join('')`, 136 | }, 137 | }, 138 | }, 139 | }, 140 | }, 141 | }, 142 | ] 143 | ``` 144 | 145 | The nested components can also be an array and use a name option in component which is used in you template. 146 | 147 | ## Changelog 148 | 149 | Detailed changes for each release are documented in [CHANGELOG.md](./CHANGELOG.md). 150 | 151 | ## License 152 | 153 | [MIT][] © [JounQin][]@[1stG.me][] 154 | 155 | [1stg.me]: https://www.1stg.me 156 | [jounqin]: https://GitHub.com/JounQin 157 | [mit]: http://opensource.org/licenses/MIT 158 | -------------------------------------------------------------------------------- /codechecks.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | - name: build-size-watcher 3 | options: 4 | files: 5 | - path: lib/*.* 6 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Vue Dynamic Demo 6 | 7 | 8 |
9 | 10 | 11 | 12 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-dynamic", 3 | "version": "0.4.0", 4 | "description": "Load stringified or normal Vue components dynamically!", 5 | "repository": "git+https://github.com/JounQin/vue-dynamic.git", 6 | "author": "JounQin ", 7 | "license": "MIT", 8 | "main": "lib/cjs", 9 | "module": "lib", 10 | "es2015": "lib/es2015", 11 | "fesm5": "lib/esm", 12 | "jsdelivr": "lib/umd", 13 | "unpkg": "lib/umd", 14 | "types": "lib", 15 | "files": [ 16 | "lib", 17 | "!*.tsbuildinfo" 18 | ], 19 | "keywords": [ 20 | "vue", 21 | "dynamic", 22 | "component", 23 | "dynamic components" 24 | ], 25 | "scripts": { 26 | "build": "run-p build:*", 27 | "build:r": "r -p", 28 | "build:ts": "tsc -P .", 29 | "lint": "run-p lint:*", 30 | "lint:es": "cross-env PARSER_NO_WATCH=true eslint . --cache --ext js,md,ts -f friendly", 31 | "lint:tsc": "tsc --noEmit" 32 | }, 33 | "peerDependencies": { 34 | "vue": "^2.0.0" 35 | }, 36 | "devDependencies": { 37 | "@1stg/lib-config": "^0.5.1", 38 | "npm-run-all": "^4.1.5", 39 | "typescript": "^3.7.5", 40 | "vue": "^2.6.11" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /scripts/deploy.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | set -e 4 | 5 | PKG_VERSION=$(jq -r '.version' package.json) 6 | 7 | git fetch origin v"$PKG_VERSION" || { 8 | yarn global add standard-version 9 | standard-version -a --release-as "$PKG_VERSION" 10 | git push --follow-tags origin "$GH_BRANCH" 11 | npm publish 12 | } 13 | -------------------------------------------------------------------------------- /src/dynamic.ts: -------------------------------------------------------------------------------- 1 | /* eslint-disable @typescript-eslint/no-explicit-any */ 2 | import Vue, { ComponentOptions } from 'vue' 3 | 4 | import { isFunction, isObject, warn } from './utils' 5 | 6 | const objCompsToArr = (objComponents: ComponentOptions['components']) => { 7 | const components: Array> = [] 8 | for (const [key, value] of Object.entries(objComponents!)) { 9 | if (isObject(value)) { 10 | components.push(Object.assign(value, { name: key })) 11 | } 12 | } 13 | return components 14 | } 15 | 16 | const invalidMsg = (msg: string) => warn(`invalid ${msg} will be ignored!`) 17 | const nonMsg = (msg: string) => warn(`no ${msg} found thus it will be ignored!`) 18 | 19 | const generateField = ( 20 | comp: ComponentOptions, 21 | component: ComponentOptions, 22 | type: 'filters' | 'methods', 23 | ) => { 24 | const field = comp[type] 25 | if (isObject(field)) { 26 | const wrappedField: Record any> = {} 27 | for (const [fieldName, method] of Object.entries(field || {})) { 28 | // prettier-ignore 29 | wrappedField[fieldName] = isFunction(method) 30 | ? method 31 | // @ts-ignore 32 | : Function[Array.isArray(method) ? 'apply' : 'call'](null, method) 33 | } 34 | component[type] = wrappedField 35 | } else if (field) { 36 | return invalidMsg(type) 37 | } 38 | return true 39 | } 40 | 41 | const buildComponent = ( 42 | comps: ComponentOptions['components'] | Array>, 43 | notFirst = false, 44 | // eslint-disable-next-line sonarjs/cognitive-complexity 45 | ): ComponentOptions | ComponentOptions['components'] | void => { 46 | if (!comps) { 47 | return 48 | } 49 | 50 | if (isObject(comps)) { 51 | comps = objCompsToArr(comps as ComponentOptions['components']) 52 | } else if (!Array.isArray(comps)) { 53 | return invalidMsg('components') 54 | } 55 | 56 | if (comps.length === 0) { 57 | return nonMsg('components') 58 | } 59 | 60 | let wrapTemp = '' 61 | const wrapComp: ComponentOptions['components'] = {} 62 | 63 | let count = 0 64 | comps.forEach((comp, index) => { 65 | const { name = `Dynamic__${index}`, template, data, components } = comp 66 | 67 | if (!template) { 68 | return nonMsg('template') 69 | } 70 | 71 | wrapTemp += `<${name}${notFirst ? '' : ' v-on="$parent.$listeners"'} />` 72 | const component: ComponentOptions = (wrapComp[name] = { template }) 73 | 74 | if ( 75 | !generateField(comp, component, 'filters') || 76 | !generateField(comp, component, 'methods') 77 | ) { 78 | return 79 | } 80 | 81 | if (data) { 82 | component.data = isFunction(data) ? data : () => ({ ...data }) 83 | } 84 | 85 | if (components) { 86 | component.components = buildComponent( 87 | components, 88 | true, 89 | ) as ComponentOptions['components'] 90 | } 91 | 92 | count++ 93 | }) 94 | 95 | if (!count) { 96 | return 97 | } 98 | 99 | return notFirst 100 | ? wrapComp 101 | : { 102 | name: 'Dynamic__Root', 103 | template: count === 1 ? wrapTemp : `
${wrapTemp}
`, 104 | components: wrapComp, 105 | } 106 | } 107 | 108 | export interface Dynamic extends Vue { 109 | emptyView: {} 110 | view: {} 111 | comps: ComponentOptions['components'] | Array> 112 | build: () => void 113 | } 114 | 115 | export const Dynamic: ComponentOptions = { 116 | name: 'vue-dynamic', 117 | template: ``, 118 | props: { 119 | comps: { 120 | validator: (value: unknown) => 121 | !value || Array.isArray(value) || isObject(value), 122 | }, 123 | emptyView: { 124 | required: true, 125 | validator: (value: unknown) => isObject(value), 126 | }, 127 | }, 128 | data() { 129 | return { 130 | view: this.emptyView, 131 | } 132 | }, 133 | // @ts-ignore 134 | watch: { 135 | comps: { 136 | immediate: true, 137 | // @ts-ignore 138 | handler: 'build', 139 | }, 140 | }, 141 | methods: { 142 | build() { 143 | this.view = buildComponent(this.comps) || this.emptyView 144 | }, 145 | }, 146 | } 147 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { VueConstructor } from 'vue' 2 | 3 | import { Dynamic } from './dynamic' 4 | 5 | export interface VueDynamicOptions { 6 | name?: string 7 | } 8 | 9 | export const VueDynamic = ( 10 | Vue: VueConstructor, 11 | options: VueDynamicOptions, 12 | ) => Vue.component(options?.name || 'Dynamic', Dynamic) 13 | 14 | if (typeof window !== 'undefined' && window.Vue) { 15 | // @ts-ignore 16 | window.Vue.use(VueDynamic) 17 | } 18 | 19 | export default VueDynamic 20 | 21 | export const install = VueDynamic 22 | 23 | export { Dynamic } 24 | -------------------------------------------------------------------------------- /src/utils.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | const { toString } = Object.prototype 4 | 5 | export const isFunction = (arg: unknown) => typeof arg === 'function' 6 | 7 | export const isObject = (arg: unknown) => 8 | toString.call(arg) === '[object Object]' 9 | 10 | export const { warn } = Vue.util 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./node_modules/@1stg/tsconfig/lib", 3 | "compilerOptions": { 4 | "baseUrl": ".", 5 | "outDir": "lib" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | 3 | declare module 'vue/types/vue' { 4 | interface VueConstructor { 5 | util: { 6 | warn: (...args: unknown[]) => void 7 | } 8 | } 9 | } 10 | --------------------------------------------------------------------------------