├── jest-setup.ts ├── .vscode └── settings.json ├── __tests__ ├── tsconfig.json ├── v-model.spec.tsx └── jsx.spec.tsx ├── index.js ├── jsx-runtime.js ├── jsx-dev-runtime.js ├── global.d.ts ├── babel.config.js ├── src ├── index.ts ├── dev.ts └── jsx.ts ├── jest.config.js ├── .github └── workflows │ └── test.yml ├── api-extractor.json ├── LICENSE ├── .gitignore ├── package.json ├── rollup.config.js ├── tsconfig.json └── README.md /jest-setup.ts: -------------------------------------------------------------------------------- 1 | import 'regenerator-runtime/runtime'; 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib" 3 | } -------------------------------------------------------------------------------- /__tests__/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "./" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./dist/vue-jsx-runtime.prod.cjs') 5 | } else { 6 | module.exports = require('./dist/vue-jsx-runtime.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /jsx-runtime.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./dist/vue-jsx-runtime.prod.cjs') 5 | } else { 6 | module.exports = require('./dist/vue-jsx-runtime.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /jsx-dev-runtime.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | if (process.env.NODE_ENV === 'production') { 4 | module.exports = require('./dist/vue-jsx-runtime.prod.cjs') 5 | } else { 6 | module.exports = require('./dist/vue-jsx-runtime.cjs') 7 | } 8 | -------------------------------------------------------------------------------- /global.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'vue/jsx-runtime' { 2 | export const jsx: Function 3 | export const jsxs: Function 4 | export const jsxDEV: Function 5 | } 6 | declare module 'vue/jsx-dev-runtime' { 7 | export const jsx: Function 8 | export const jsxs: Function 9 | export const jsxDEV: Function 10 | } 11 | 12 | declare var __DEV__: boolean 13 | -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | /* istanbul ignore next */ 2 | module.exports = { 3 | presets: [ 4 | '@babel/preset-env', 5 | ], 6 | plugins: [ 7 | [ 8 | '@babel/plugin-transform-react-jsx', 9 | { 10 | throwIfNamespace: false, // defaults to true 11 | runtime: 'automatic', // defaults to classic 12 | importSource: 'vue' // defaults to react 13 | } 14 | ], 15 | ], 16 | }; 17 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { Fragment } from 'vue' 2 | import { jsx as prodJsx, jsxs as prodJsxs } from './jsx' 3 | import { 4 | jsxWithValidationStatic, 5 | jsxWithValidationDynamic, 6 | jsxWithValidation 7 | } from './dev' 8 | 9 | // istanbul ignore next 10 | const jsx = __DEV__ ? jsxWithValidationDynamic : prodJsx 11 | // istanbul ignore next 12 | const jsxs = __DEV__ ? jsxWithValidationStatic : prodJsxs 13 | // istanbul ignore next 14 | const jsxDEV = __DEV__ ? jsxWithValidation : undefined 15 | 16 | export { Fragment, jsx, jsxs, jsxDEV } 17 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | collectCoverage: true, 3 | coverageDirectory: 'coverage', 4 | coverageReporters: ['html', 'lcov', 'text'], 5 | collectCoverageFrom: [ 6 | 'src/*.ts' 7 | ], 8 | coveragePathIgnorePatterns: [ 9 | '/node_modules/', 10 | '\\.d\\.ts$' 11 | ], 12 | rootDir: __dirname, 13 | testEnvironment: 'jsdom', 14 | setupFiles: ['./jest-setup.ts'], 15 | moduleNameMapper: { 16 | "vue/jsx-runtime": ["/src"], 17 | "vue/jsx-dev-runtime": ["/src"] 18 | }, 19 | transform: { 20 | '\\.(ts|tsx)$': 'ts-jest', 21 | }, 22 | globals: { 23 | __DEV__: true, 24 | 'ts-jest': { 25 | babelConfig: true, 26 | }, 27 | }, 28 | }; 29 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | pull_request: 8 | branches: 9 | - main 10 | 11 | jobs: 12 | test: 13 | runs-on: ubuntu-latest 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Install pnpm 18 | uses: pnpm/action-setup@v2.0.1 19 | with: 20 | version: 6.15.1 21 | 22 | - name: Set node version to 16 23 | uses: actions/setup-node@v2 24 | with: 25 | node-version: 16 26 | cache: 'pnpm' 27 | 28 | - run: pnpm bootstrap 29 | 30 | - name: Run unit tests 31 | run: pnpm run test 32 | 33 | - uses: codecov/codecov-action@v2 34 | -------------------------------------------------------------------------------- /api-extractor.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json", 3 | 4 | "mainEntryPointFilePath": "./dist/index.d.ts", 5 | 6 | "apiReport": { 7 | "enabled": true, 8 | "reportFolder": "/temp/" 9 | }, 10 | 11 | "docModel": { 12 | "enabled": true 13 | }, 14 | 15 | "dtsRollup": { 16 | "enabled": true, 17 | "untrimmedFilePath": "./dist/vue-jsx-runtime.d.ts" 18 | }, 19 | 20 | "tsdocMetadata": { 21 | "enabled": false 22 | }, 23 | 24 | "messages": { 25 | "compilerMessageReporting": { 26 | "default": { 27 | "logLevel": "warning" 28 | } 29 | }, 30 | 31 | "extractorMessageReporting": { 32 | "default": { 33 | "logLevel": "warning", 34 | "addToApiReportFile": true 35 | }, 36 | 37 | "ae-missing-release-tag": { 38 | "logLevel": "none" 39 | } 40 | }, 41 | 42 | "tsdocMessageReporting": { 43 | "default": { 44 | "logLevel": "warning" 45 | } 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 doly mood 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 | -------------------------------------------------------------------------------- /src/dev.ts: -------------------------------------------------------------------------------- 1 | import { VNodeTypes } from 'vue' 2 | import { jsx } from './jsx' 3 | 4 | export function jsxDEV( 5 | type: VNodeTypes, 6 | config: Record = {}, 7 | maybeKey?: string, 8 | source?: object, 9 | self?: any 10 | ) { 11 | // istanbul ignore next 12 | if (__DEV__) { 13 | return jsx(type, config, maybeKey, source, self) 14 | } 15 | } 16 | 17 | export function jsxWithValidation( 18 | type: VNodeTypes, 19 | props: Record = {}, 20 | key?: string, 21 | isStaticChildren?: boolean, 22 | source?: object, 23 | self?: any 24 | ) { 25 | return jsxDEV(type, props, key, source, self) 26 | } 27 | 28 | // istanbul ignore next 29 | export function jsxWithValidationStatic( 30 | type: VNodeTypes, 31 | props: Record = {}, 32 | key?: string 33 | ) { 34 | if (__DEV__) { 35 | return jsxWithValidation(type, props, key, true) 36 | } 37 | } 38 | 39 | // istanbul ignore next 40 | export function jsxWithValidationDynamic( 41 | type: VNodeTypes, 42 | props: Record = {}, 43 | key?: string 44 | ) { 45 | if (__DEV__) { 46 | return jsxWithValidation(type, props, key, false) 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | dist 84 | temp 85 | 86 | # Gatsby files 87 | .cache/ 88 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 89 | # https://nextjs.org/blog/next-9-1#public-directory-support 90 | # public 91 | 92 | # vuepress build output 93 | .vuepress/dist 94 | 95 | # Serverless directories 96 | .serverless/ 97 | 98 | # FuseBox cache 99 | .fusebox/ 100 | 101 | # DynamoDB Local files 102 | .dynamodb/ 103 | 104 | # TernJS port file 105 | .tern-port 106 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-jsx-runtime", 3 | "version": "0.1.0", 4 | "description": "vue jsx runtime support", 5 | "main": "index.js", 6 | "module": "dist/vue-jsx-runtime.mjs", 7 | "unpkg": "dist/vue-jsx-runtime.iife.prod.js", 8 | "jsdelivr": "dist/vue-jsx-runtime.iife.prod.js", 9 | "types": "dist/vue-jsx-runtime.d.ts", 10 | "exports": { 11 | ".": { 12 | "browser": "./dist/vue-jsx-runtime.esm-browser.js", 13 | "node": { 14 | "import": { 15 | "production": "./dist/vue-jsx-runtime.prod.cjs", 16 | "development": "./dist/vue-jsx-runtime.mjs", 17 | "default": "./dist/vue-jsx-runtime.mjs" 18 | }, 19 | "require": { 20 | "production": "./dist/vue-jsx-runtime.prod.cjs", 21 | "development": "./dist/vue-jsx-runtime.cjs", 22 | "default": "./index.js" 23 | } 24 | }, 25 | "import": "./dist/vue-jsx-runtime.mjs" 26 | }, 27 | "./jsx-runtime": { 28 | "browser": "./dist/vue-jsx-runtime.esm-browser.js", 29 | "node": { 30 | "import": { 31 | "production": "./dist/vue-jsx-runtime.prod.cjs", 32 | "development": "./dist/vue-jsx-runtime.mjs", 33 | "default": "./dist/vue-jsx-runtime.mjs" 34 | }, 35 | "require": { 36 | "production": "./dist/vue-jsx-runtime.prod.cjs", 37 | "development": "./dist/vue-jsx-runtime.cjs", 38 | "default": "./index.js" 39 | } 40 | }, 41 | "import": "./dist/vue-jsx-runtime.mjs" 42 | }, 43 | "./jsx-dev-runtime": { 44 | "browser": "./dist/vue-jsx-runtime.esm-browser.js", 45 | "node": { 46 | "import": { 47 | "production": "./dist/vue-jsx-runtime.prod.cjs", 48 | "development": "./dist/vue-jsx-runtime.mjs", 49 | "default": "./dist/vue-jsx-runtime.mjs" 50 | }, 51 | "require": { 52 | "production": "./dist/vue-jsx-runtime.prod.cjs", 53 | "development": "./dist/vue-jsx-runtime.cjs", 54 | "default": "./index.js" 55 | } 56 | }, 57 | "import": "./dist/vue-jsx-runtime.mjs" 58 | }, 59 | "./package.json": "./package.json", 60 | "./dist/*": "./dist/*" 61 | }, 62 | "sideEffects": false, 63 | "scripts": { 64 | "bootstrap": "pnpm install --shamefully-hoist", 65 | "release": "pnpm test && pnpm build && pnpm publish --access=public", 66 | "build": "rm -rf dist && pnpm build:js && pnpm build:dts", 67 | "build:js": "rollup -c ./rollup.config.js", 68 | "build:dts": "api-extractor run --local --verbose", 69 | "test": "pnpm test:unit", 70 | "test:unit": "jest --coverage" 71 | }, 72 | "files": [ 73 | "dist", 74 | "jsx-runtime.js", 75 | "jsx-dev-runtime.js" 76 | ], 77 | "repository": { 78 | "type": "git", 79 | "url": "git+https://github.com/dolymood/vue-jsx-runtime.git" 80 | }, 81 | "keywords": [ 82 | "jsx", 83 | "jsx-runtime", 84 | "vue" 85 | ], 86 | "peerDependencies": { 87 | "vue": "3" 88 | }, 89 | "license": "MIT", 90 | "bugs": { 91 | "url": "https://github.com/dolymood/vue-jsx-runtime/issues" 92 | }, 93 | "homepage": "https://github.com/dolymood/vue-jsx-runtime", 94 | "devDependencies": { 95 | "@babel/core": "^7.16.0", 96 | "@babel/plugin-transform-react-jsx": "^7.16.0", 97 | "@babel/preset-env": "^7.16.4", 98 | "@microsoft/api-extractor": "^7.19.2", 99 | "@rollup/plugin-alias": "^3.1.8", 100 | "@rollup/plugin-commonjs": "^21.0.1", 101 | "@rollup/plugin-node-resolve": "^13.1.1", 102 | "@rollup/plugin-replace": "^3.0.0", 103 | "@types/jest": "^27.0.3", 104 | "@vue/shared": "^3.2.24", 105 | "@vue/test-utils": "2.0.0-beta.2", 106 | "jest": "^27.4.4", 107 | "regenerator-runtime": "^0.13.9", 108 | "rollup": "^2.61.1", 109 | "rollup-plugin-terser": "^7.0.2", 110 | "rollup-plugin-typescript2": "^0.31.1", 111 | "ts-jest": "^27.1.1", 112 | "typescript": "^4.5.3", 113 | "vue": "^3.2.24" 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | import path from 'path' 3 | import ts from 'rollup-plugin-typescript2' 4 | import replace from '@rollup/plugin-replace' 5 | import resolve from '@rollup/plugin-node-resolve' 6 | import commonjs from '@rollup/plugin-commonjs' 7 | 8 | const pkg = require('./package.json') 9 | const name = pkg.name 10 | 11 | const banner = `/*! 12 | * ${pkg.name} v${pkg.version} 13 | * (c) ${new Date().getFullYear()} dolymood (dolymood@gmail.com) 14 | * @license MIT 15 | */` 16 | 17 | // ensure TS checks only once for each build 18 | let hasTSChecked = false 19 | 20 | const outputConfigs = { 21 | // each file name has the format: `dist/${name}.${format}.js` 22 | // format being a key of this object 23 | mjs: { 24 | file: pkg.module, 25 | format: `es`, 26 | }, 27 | cjs: { 28 | file: pkg.module.replace('mjs', 'cjs'), 29 | format: `cjs`, 30 | }, 31 | global: { 32 | file: pkg.unpkg, 33 | format: `iife`, 34 | }, 35 | browser: { 36 | file: `dist/${name}.esm-browser.js`, 37 | format: `es`, 38 | }, 39 | } 40 | 41 | const packageBuilds = Object.keys(outputConfigs) 42 | const packageConfigs = packageBuilds.map((format) => 43 | createConfig(format, outputConfigs[format]) 44 | ) 45 | 46 | // only add the production ready if we are bundling the options 47 | packageBuilds.forEach((buildName) => { 48 | if (buildName === 'cjs') { 49 | packageConfigs.push(createProductionConfig(buildName)) 50 | } else if (buildName === 'global') { 51 | packageConfigs.push(createMinifiedConfig(buildName)) 52 | } 53 | }) 54 | 55 | export default packageConfigs 56 | 57 | function createConfig(buildName, output, plugins = []) { 58 | output.sourcemap = !!process.env.SOURCE_MAP 59 | output.banner = banner 60 | output.externalLiveBindings = false 61 | output.globals = { 62 | 'vue': 'Vue' 63 | } 64 | 65 | const isProductionBuild = /\.prod\.[cmj]s$/.test(output.file) 66 | const isGlobalBuild = buildName === 'global' 67 | const isRawESMBuild = buildName === 'browser' 68 | const isNodeBuild = buildName === 'cjs' 69 | const isBundlerESMBuild = buildName === 'browser' || buildName === 'mjs' 70 | 71 | if (isGlobalBuild) output.name = 'VueJSXRuntime' 72 | 73 | const shouldEmitDeclarations = !hasTSChecked 74 | 75 | const tsPlugin = ts({ 76 | check: !hasTSChecked, 77 | tsconfig: path.resolve(__dirname, './tsconfig.json'), 78 | cacheRoot: path.resolve(__dirname, './node_modules/.rts2_cache'), 79 | tsconfigOverride: { 80 | compilerOptions: { 81 | sourceMap: output.sourcemap, 82 | declaration: shouldEmitDeclarations, 83 | declarationMap: shouldEmitDeclarations, 84 | }, 85 | exclude: ['__tests__'], 86 | }, 87 | }) 88 | // we only need to check TS and generate declarations once for each build. 89 | // it also seems to run into weird issues when checking multiple times 90 | // during a single build. 91 | hasTSChecked = true 92 | 93 | const external = ['vue'] 94 | if (!isGlobalBuild) { 95 | external.push.apply(external, ['@vue/shared']) 96 | } 97 | 98 | const nodePlugins = [resolve(), commonjs()] 99 | 100 | return { 101 | input: `src/index.ts`, 102 | // Global and Browser ESM builds inlines everything so that they can be 103 | // used alone. 104 | external, 105 | plugins: [ 106 | tsPlugin, 107 | createReplacePlugin( 108 | isProductionBuild, 109 | isBundlerESMBuild, 110 | // isBrowserBuild? 111 | isNodeBuild 112 | ), 113 | ...nodePlugins, 114 | ...plugins, 115 | ], 116 | output, 117 | // onwarn: (msg, warn) => { 118 | // if (!/Circular/.test(msg)) { 119 | // warn(msg) 120 | // } 121 | // }, 122 | } 123 | } 124 | 125 | function createReplacePlugin( 126 | isProduction, 127 | isBundlerESMBuild, 128 | isNodeBuild 129 | ) { 130 | const replacements = { 131 | __DEV__: 132 | isBundlerESMBuild || (isNodeBuild && !isProduction) 133 | ? // preserve to be handled by bundlers 134 | `(process.env.NODE_ENV !== 'production')` 135 | : // hard coded dev/prod builds 136 | JSON.stringify(!isProduction) 137 | } 138 | // allow inline overrides like 139 | //__RUNTIME_COMPILE__=true yarn build 140 | Object.keys(replacements).forEach((key) => { 141 | if (key in process.env) { 142 | replacements[key] = process.env[key] 143 | } 144 | }) 145 | return replace({ 146 | preventAssignment: true, 147 | values: replacements, 148 | }) 149 | } 150 | 151 | function createProductionConfig(format) { 152 | const extension = format === 'cjs' ? 'cjs' : 'js' 153 | const descriptor = format === 'cjs' ? '' : `.${format}` 154 | return createConfig(format, { 155 | file: `dist/${name}${descriptor}.prod.${extension}`, 156 | format: outputConfigs[format].format, 157 | }) 158 | } 159 | 160 | function createMinifiedConfig(format) { 161 | const { terser } = require('rollup-plugin-terser') 162 | return createConfig( 163 | format, 164 | { 165 | file: `dist/${name}.${format === 'global' ? 'iife' : format}.prod.js`, 166 | format: outputConfigs[format].format, 167 | }, 168 | [ 169 | terser({ 170 | module: /^esm/.test(format), 171 | compress: { 172 | ecma: 2015, 173 | pure_getters: true, 174 | }, 175 | }), 176 | ] 177 | ) 178 | } 179 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "ES2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', 'ES2021', or 'ESNEXT'. */ 8 | "module": "ESNext", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | "lib": ["ESNext", "DOM"], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | "jsx": "react-jsxdev", /* Specify JSX code generation: 'preserve', 'react-native', 'react', 'react-jsx' or 'react-jsxdev'. */ 13 | "jsxImportSource": "vue", 14 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 15 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 16 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 17 | // "outFile": "./", /* Concatenate and emit output to single file. */ 18 | "outDir": "dist", /* Redirect output structure to the directory. */ 19 | "rootDirs": ["./src"], /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 20 | // "composite": true, /* Enable project compilation */ 21 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 22 | // "removeComments": true, /* Do not emit comments to output. */ 23 | // "noEmit": true, /* Do not emit outputs. */ 24 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 25 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 26 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 27 | 28 | /* Strict Type-Checking Options */ 29 | "strict": true, /* Enable all strict type-checking options. */ 30 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 31 | // "strictNullChecks": true, /* Enable strict null checks. */ 32 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 33 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 34 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 35 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 36 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 37 | 38 | /* Additional Checks */ 39 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 40 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 41 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 42 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 43 | // "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 44 | // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an 'override' modifier. */ 45 | // "noPropertyAccessFromIndexSignature": true, /* Require undeclared properties from index signatures to use element accesses. */ 46 | 47 | /* Module Resolution Options */ 48 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 49 | // "baseUrl": ".", /* Base directory to resolve non-absolute module names. */ 50 | "paths": { 51 | "vue/jsx-runtime": ["./src/"], 52 | "vue/jsx-dev-runtime": ["./src/"] 53 | }, 54 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 55 | // "typeRoots": [], /* List of folders to include type definitions from. */ 56 | "types": ["jest", "node"], /* Type declaration files to be included in compilation. */ 57 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 58 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 59 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 60 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 61 | 62 | /* Source Map Options */ 63 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 64 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 65 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 66 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 67 | 68 | /* Experimental Options */ 69 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 70 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 71 | 72 | /* Advanced Options */ 73 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 74 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 75 | }, 76 | "include": [ 77 | "src", 78 | "global.d.ts" 79 | ], 80 | "exclude": [ 81 | "node_modules" 82 | ] 83 | } 84 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-jsx-runtime [![npm](https://badgen.net/npm/v/vue-jsx-runtime)](https://www.npmjs.com/package/vue-jsx-runtime) [![build status](https://github.com/dolymood/vue-jsx-runtime/workflows/test/badge.svg)](https://github.com/dolymood/vue-jsx-runtime/actions/workflows/test.yml) [![coverage](https://badgen.net/codecov/c/github/dolymood/vue-jsx-runtime)](https://codecov.io/github/dolymood/vue-jsx-runtime) 2 | 3 | Vue 3 jsx runtime support. 4 | 5 | The background https://reactjs.org/blog/2020/09/22/introducing-the-new-jsx-transform.html . With new jsx runtime support, which means a JSX ast standard, every lib can have its own jsx syntax with small limits. 6 | 7 | [Examples](https://github.com/dolymood/vue-jsx-runtime-examples) with TS: 8 | 9 | - [vite](https://github.com/dolymood/vue-jsx-runtime-examples/tree/main/vite) with esbuild usage 10 | - [vue-cli](https://github.com/dolymood/vue-jsx-runtime-examples/tree/main/vue-cli) with babel usage 11 | - [swc](https://github.com/dolymood/vue-jsx-runtime-examples/tree/main/swc) 12 | 13 | TODO: 14 | 15 | - optimize, transformOn, isCustomElement ... 16 | - dev validation 17 | - more tests 18 | - more features 19 | 20 | ## Installation 21 | 22 | Install the plugin with: 23 | 24 | ```bash 25 | pnpm add vue-jsx-runtime 26 | # or 27 | npm install vue-jsx-runtime 28 | ``` 29 | 30 | ## Usage 31 | 32 | ### In Babel 33 | 34 | ```js 35 | // babel plugin 36 | plugins: [ 37 | [ 38 | // add @babel/plugin-transform-react-jsx 39 | '@babel/plugin-transform-react-jsx', 40 | { 41 | throwIfNamespace: false, 42 | runtime: 'automatic', 43 | importSource: 'vue-jsx-runtime' 44 | } 45 | ], 46 | ], 47 | ``` 48 | ### In TypeScript 49 | 50 | `tsconfig.json`: 51 | 52 | ```json 53 | { 54 | "compilerOptions": { 55 | "jsx": "react-jsxdev", /* 'react-jsx' or 'react-jsxdev'. You can also use 'preserve' to use babel or other tools to handle jsx*/ 56 | "jsxImportSource": "vue-jsx-runtime" 57 | } 58 | } 59 | ``` 60 | 61 | If you used with Babel, you need to set the config: 62 | 63 | ```json 64 | { 65 | "compilerOptions": { 66 | "jsx": "preserve", /* 'react-jsx' or 'react-jsxdev'. You can also use 'preserve' to use babel or other tools to handle jsx*/ 67 | } 68 | } 69 | ``` 70 | 71 | ### In Other Tools 72 | 73 | If you use some tool which support jsx-runtime, like [swc](https://swc.rs/), you can use like this: 74 | 75 | `.swcrc`: 76 | 77 | ```json 78 | { 79 | "jsc": { 80 | "transform": { 81 | "react": { 82 | "runtime": "automatic", 83 | "importSource": "vue-jsx-runtime" 84 | } 85 | } 86 | } 87 | } 88 | ``` 89 | 90 | More details, see https://swc.rs/docs/configuration/compilation#jsctransformreact 91 | 92 | About [esbuild](https://github.com/evanw/esbuild/), see https://github.com/evanw/esbuild/issues/1172 and a hack way https://github.com/evanw/esbuild/issues/832 . https://github.com/evanw/esbuild/issues/334#issuecomment-711444731 . 93 | 94 | ## Syntax 95 | 96 | ### Content 97 | 98 | Functional component: 99 | 100 | ```jsx 101 | const App = () =>
Vue 3.0
; 102 | ``` 103 | 104 | with render 105 | 106 | ```jsx 107 | const App = { 108 | render() { 109 | return
Vue 3.0
; 110 | }, 111 | } 112 | ``` 113 | 114 | ```jsx 115 | import { withModifiers, defineComponent } from "vue"; 116 | 117 | const App = defineComponent({ 118 | setup() { 119 | const count = ref(0); 120 | 121 | const inc = () => { 122 | count.value++; 123 | }; 124 | 125 | return () => ( 126 |
{count.value}
127 | ); 128 | }, 129 | }); 130 | ``` 131 | 132 | Fragment 133 | 134 | ```jsx 135 | const App = () => ( 136 | <> 137 | I'm 138 | Fragment 139 | 140 | ); 141 | ``` 142 | 143 | ### Attributes / Props 144 | 145 | ```jsx 146 | const App = () => ; 147 | ``` 148 | 149 | with a dynamic binding: 150 | 151 | ```jsx 152 | const placeholderText = "email"; 153 | const App = () => ; 154 | ``` 155 | 156 | ### Directives 157 | 158 | #### v-show 159 | 160 | ```jsx 161 | const App = { 162 | data() { 163 | return { visible: true }; 164 | }, 165 | render() { 166 | return ; 167 | }, 168 | }; 169 | ``` 170 | 171 | #### v-model 172 | 173 | A little different with `@vue/babel-plugin-jsx`. 174 | 175 | Syntax: 176 | ``` 177 | v-model={[object, ["path/key"], argument, ["modifier"]]} 178 | ``` 179 | 180 | ##### Recommend: 181 | 182 | ```jsx 183 | const val = ref(1); // val.value will be 1 184 | // jsx 185 | // do not use v-model={val.value} 186 | ``` 187 | 188 | ```jsx 189 | 190 | ``` 191 | 192 | `v-model` will use `val["value"]` to getter or setter by default. 193 | 194 | ##### Other usage 195 | 196 | ```jsx 197 | const val = ref(1); 198 | 199 | 200 | ``` 201 | 202 | ```jsx 203 | 204 | ``` 205 | 206 | Will compile to: 207 | 208 | ```js 209 | h(A, { 210 | argument: val["value"], 211 | argumentModifiers: { 212 | modifier: true, 213 | }, 214 | "onUpdate:argument": ($event) => (val["value"] = $event), 215 | }); 216 | ``` 217 | 218 | #### custom directive 219 | 220 | Recommended when using string arguments 221 | 222 | ```jsx 223 | const App = { 224 | directives: { custom: customDirective }, 225 | setup() { 226 | return () => ; 227 | }, 228 | }; 229 | ``` 230 | 231 | ```jsx 232 | const App = { 233 | directives: { custom: customDirective }, 234 | setup() { 235 | return () => ; 236 | }, 237 | }; 238 | ``` 239 | 240 | ### Slot 241 | 242 | #### Recommend 243 | 244 | Use object slots: 245 | 246 | ```jsx 247 | const A = (props, { slots }) => ( 248 | <> 249 |

{ slots.default ? slots.default() : 'foo' }

250 |

{ slots.bar?.() }

251 | 252 | ); 253 | 254 | const App = { 255 | setup() { 256 | return () => ( 257 | <> 258 |
259 | {{ 260 | default: () =>
A
, 261 | bar: () => B, 262 | }} 263 |
264 | {() => "foo"} 265 | 266 | ); 267 | }, 268 | }; 269 | ``` 270 | 271 | #### Use v-slots 272 | 273 | > Note: In `jsx`, _`v-slot`_ should be replace with **`v-slots`** 274 | 275 | ```jsx 276 | const App = { 277 | setup() { 278 | const slots = { 279 | bar: () => B, 280 | }; 281 | return () => ( 282 | 283 |
A
284 |
285 | ); 286 | }, 287 | }; 288 | // or 289 | const App = { 290 | setup() { 291 | const slots = { 292 | default: () =>
A
, 293 | bar: () => B, 294 | }; 295 | return () => ; 296 | }, 297 | }; 298 | ``` 299 | 300 | ## Different with [vue jsx-next](https://github.com/vuejs/jsx-next) 301 | 302 | - `jsx-next` is a plugin for `Babel` only. 303 | - `vue-jsx-runtime` can be used with `Babel`, `TypeScript`, `swc`, `esbuild` and more. 304 | 305 | `vue-jsx-runtime` limits: 306 | 307 | - can not merge ele/component props 308 | - `v-model` syntax is little different with `jsx-next` - `v-model` 309 | -------------------------------------------------------------------------------- /__tests__/v-model.spec.tsx: -------------------------------------------------------------------------------- 1 | // use https://github.com/vuejs/jsx-next/tree/dev/packages/babel-plugin-jsx test cases 2 | import { shallowMount, mount } from '@vue/test-utils' 3 | import { defineComponent, VNode, ref } from 'vue' 4 | 5 | // v-model={[context, path, arg, modifers]} 6 | 7 | test('input[type="checkbox"] should work', async () => { 8 | const wrapper = shallowMount({ 9 | data() { 10 | return { 11 | test: true, 12 | } 13 | }, 14 | render() { 15 | return 16 | }, 17 | }, { 18 | // fix jsdom https://github.com/jsdom/jsdom/commit/1da0a4c1f4a0e9f8fa5576a9c5e2897b8307f7ab 19 | attachTo: document.body 20 | }) 21 | 22 | expect(wrapper.vm.$el.checked).toBe(true) 23 | wrapper.vm.test = false 24 | await wrapper.vm.$nextTick() 25 | expect(wrapper.vm.$el.checked).toBe(false) 26 | expect(wrapper.vm.test).toBe(false) 27 | await wrapper.trigger('click') 28 | expect(wrapper.vm.$el.checked).toBe(true) 29 | expect(wrapper.vm.test).toBe(true) 30 | }) 31 | 32 | test('input[type="radio"] should work', async () => { 33 | const test = ref('1') 34 | const wrapper = mount({ 35 | setup() { 36 | return () => { 37 | return ( 38 | <> 39 | 40 | 41 | 42 | ) 43 | } 44 | } 45 | }, { 46 | // fix jsdom https://github.com/jsdom/jsdom/commit/1da0a4c1f4a0e9f8fa5576a9c5e2897b8307f7ab 47 | attachTo: document.body 48 | }) 49 | 50 | const [a, b] = wrapper.vm.$.subTree.children as VNode[] 51 | 52 | expect(a.el!.checked).toBe(true) 53 | // wrapper.vm.test = '2' 54 | test.value = '2' 55 | await wrapper.vm.$nextTick() 56 | expect(a.el!.checked).toBe(false) 57 | expect(b.el!.checked).toBe(true) 58 | await a.el!.click() 59 | expect(a.el!.checked).toBe(true) 60 | expect(b.el!.checked).toBe(false) 61 | // expect(wrapper.vm.test).toBe('1') 62 | expect(test.value).toBe('1') 63 | }) 64 | 65 | test('select should work with value bindings', async () => { 66 | const wrapper = shallowMount({ 67 | data: () => ({ 68 | test: 2, 69 | }), 70 | render() { 71 | return ( 72 | 77 | ) 78 | }, 79 | }) 80 | 81 | const el = wrapper.vm.$el 82 | 83 | expect(el.value).toBe('2') 84 | expect(el.children[1].selected).toBe(true) 85 | wrapper.vm.test = 3 86 | await wrapper.vm.$nextTick() 87 | expect(el.value).toBe('3') 88 | expect(el.children[2].selected).toBe(true) 89 | 90 | el.value = '1' 91 | await wrapper.trigger('change') 92 | expect(wrapper.vm.test).toBe('1') 93 | 94 | el.value = '2' 95 | await wrapper.trigger('change') 96 | expect(wrapper.vm.test).toBe(2) 97 | }) 98 | 99 | test('textarea should update value both ways', async () => { 100 | const wrapper = shallowMount({ 101 | data: () => ({ 102 | test: 'b', 103 | }), 104 | render() { 105 | return