From dafaccbe291f8cc1db9716827366ddd418637f40 Mon Sep 17 00:00:00 2001 From: Evan You Date: Tue, 19 May 2020 10:58:39 -0400 Subject: [PATCH] feat: build-in dev server proxy support close #147 --- README.md | 46 +++++++++++++----- package.json | 1 + src/node/config.ts | 63 +++++++++++++++++++++--- src/node/server/index.ts | 2 + src/node/server/serverPluginProxy.ts | 27 +++++++++++ yarn.lock | 71 +++++++++++++++++++++++++++- 6 files changed, 190 insertions(+), 20 deletions(-) create mode 100644 src/node/server/serverPluginProxy.ts diff --git a/README.md b/README.md index b36b6e93d507ad..771a202bcbb36a 100644 --- a/README.md +++ b/README.md @@ -50,6 +50,8 @@ Vite assumes you are targeting modern browsers and therefore does not perform an - [CSS Modules](#css-modules) - [CSS Pre-processors](#css-pre-processors) - [JSX](#jsx) +- [Config File](#config-file) +- [Dev Server Proxy](#dev-server-proxy) - [Production Build](#production-build) Vite tries to mirror the default configuration in [vue-cli](http://cli.vuejs.org/) as much as possible. If you've used `vue-cli` or other webpack-based boilerplates before, you should feel right at home. That said, do expect things to be different here and there. @@ -222,6 +224,38 @@ ReactDOM.render(

Hello, what!

, document.getElementById('app')) If you need a custom JSX pragma, JSX can also be customized via `--jsx-factory` and `--jsx-fragment` flags from the CLI or `jsx: { factory, fragment }` from the API. For example, you can run `vite --jsx-factory=h` to use `h` for JSX element creation calls. +### Config File + +You can create a `vite.config.js` or `vite.config.ts` file in your project. Vite will automatically use it if one is found in the current working directory. You can also explicitly specify a config file via `vite --config my-config.js`. + +In addition to options mapped from CLI flags, it also supports `alias`, `transforms`, and plugins (which is a subset of the config interface). For now, see [config.ts](https://github.com/vuejs/vite/blob/master/src/node/config.ts) for full details before more thorough documentation is available. + +### Dev Server Proxy + +> 0.15.6+ + +You can use the `proxy` option in the config file to configure custom proxies for the dev server. Vite uses [`koa-proxies`](https://github.com/vagusX/koa-proxies) which in turn uses [`http-proxy`](https://github.com/http-party/node-http-proxy). Each key can be a path Full options [here](https://github.com/http-party/node-http-proxy#options). + +Example: + +``` js +// vite.config.js +module.exports = { + proxy: { + proxy: { + // string shorthand + '/foo': 'http://localhost:4567/foo', + // with options + '/api': { + target: 'http://jsonplaceholder.typicode.com', + changeOrigin: true, + rewrite: path => path.replace(/^\/api/, '') + } + } + } +} +``` + ### Production Build Vite does utilize bundling for production builds, because native ES module imports result in waterfall network requests that are simply too punishing for page load time in production. @@ -230,12 +264,6 @@ You can run `vite build` to bundle the app. Internally, we use a highly opinionated Rollup config to generate the build. The build is configurable by passing on most options to Rollup - and most non-rollup string/boolean options have mapping flags in the CLI (see [build/index.ts](https://github.com/vuejs/vite/blob/master/src/node/build/index.ts) for full details). -## Config File - -You can create a `vite.config.js` or `vite.config.ts` file in your project. Vite will automatically use it if one is found in the current working directory. You can also explicitly specify a config file via `vite --config my-config.js`. - -In addition to options mapped from CLI flags, it also supports `alias`, `transforms`, and plugins (which is a subset of the config interface). For now, see [config.ts](https://github.com/vuejs/vite/blob/master/src/node/config.ts) for full details before more thorough documentation is available. - ## API ### Dev Server @@ -345,12 +373,6 @@ Snowpack 2 is closer to Vite in scope - both offer bundle-free dev servers and c - While Vite can technically be used to develop apps with any framework, its main focus is to provide the best Vue development experience possible. 3rd party frameworks are supported, but not as the utmost priority. -## TODOs - -- Config file support - - Define config options - - Define plugin format - ## Trivia [vite](https://en.wiktionary.org/wiki/vite) is the french word for "fast" and is pronounced `/vit/`. diff --git a/package.json b/package.json index 3db22610893ade..009a7e7e6bf56b 100644 --- a/package.json +++ b/package.json @@ -76,6 +76,7 @@ "koa": "^2.11.0", "koa-conditional-get": "^2.0.0", "koa-etag": "^3.0.0", + "koa-proxies": "^0.11.0", "koa-send": "^5.0.0", "koa-static": "^5.0.0", "lru-cache": "^5.1.1", diff --git a/src/node/config.ts b/src/node/config.ts index e8dec4288121e1..91ef75907ea05c 100644 --- a/src/node/config.ts +++ b/src/node/config.ts @@ -13,6 +13,7 @@ import Rollup, { } from 'rollup' import { Transform } from './transform' import { DepOptimizationOptions } from './depOptimizer' +import { IKoaProxiesOptions } from 'koa-proxies' export { Resolver, Transform } @@ -28,6 +29,16 @@ export interface SharedConfig { root?: string /** * Import alias. Can only be exact mapping, does not support wildcard syntax. + * + * Example `vite.config.js`: + * ``` js + * module.exports = { + * alias: { + * 'react': '@pika/react', + * 'react-dom': '@pika/react-dom' + * } + * } + * ``` */ alias?: Record /** @@ -41,19 +52,26 @@ export interface SharedConfig { resolvers?: Resolver[] /** * Configure dep optimization behavior. + * + * Example `vite.config.js`: + * ``` js + * module.exports = { + * optimizeDeps: { + * exclude: ['dep-a', 'dep-b'] + * } + * } + * ``` */ optimizeDeps?: DepOptimizationOptions /** - * Options to pass to @vue/compiler-dom + * Options to pass to `@vue/compiler-dom` + * + * https://github.com/vuejs/vue-next/blob/master/packages/compiler-core/src/options.ts */ vueCompilerOptions?: CompilerOptions /** * Configure what to use for jsx factory and fragment. - * @default - * { - * factory: 'React.createElement', - * fragment: 'React.Fragment' - * } + * @default 'vue' */ jsx?: | 'vue' @@ -66,6 +84,32 @@ export interface SharedConfig { } export interface ServerConfig extends SharedConfig { + /** + * Configure custom proxy rules for the dev server. Uses + * [`koa-proxies`](https://github.com/vagusX/koa-proxies) which in turn uses + * [`http-proxy`](https://github.com/http-party/node-http-proxy). Each key can + * be a path Full options + * [here](https://github.com/http-party/node-http-proxy#options). + * + * Example `vite.config.js`: + * ``` js + * module.exports = { + * proxy: { + * proxy: { + * // string shorthand + * '/foo': 'http://localhost:4567/foo', + * // with options + * '/api': { + * target: 'http://jsonplaceholder.typicode.com', + * changeOrigin: true, + * rewrite: path => path.replace(/^\/api/, '') + * } + * } + * } + * } + * ``` + */ + proxy?: Record /** * Whether to use a Service Worker to cache served code. This can greatly * improve full page reload performance, but requires a Service Worker @@ -74,6 +118,10 @@ export interface ServerConfig extends SharedConfig { * @default false */ serviceWorker?: boolean + /** + * Resolved server plugins. + * @internal + */ plugins?: ServerPlugin[] } @@ -121,16 +169,19 @@ export interface BuildConfig extends SharedConfig { // The following are API only and not documented in the CLI. ----------------- /** * Will be passed to rollup.rollup() + * * https://rollupjs.org/guide/en/#big-list-of-options */ rollupInputOptions?: RollupInputOptions /** * Will be passed to bundle.generate() + * * https://rollupjs.org/guide/en/#big-list-of-options */ rollupOutputOptions?: RollupOutputOptions /** * Will be passed to rollup-plugin-vue + * * https://github.com/vuejs/rollup-plugin-vue/blob/next/src/index.ts */ rollupPluginVueOptions?: Partial diff --git a/src/node/server/index.ts b/src/node/server/index.ts index a611c0f24b250a..82c8699e449b1a 100644 --- a/src/node/server/index.ts +++ b/src/node/server/index.ts @@ -14,6 +14,7 @@ import { esbuildPlugin } from './serverPluginEsbuild' import { ServerConfig } from '../config' import { createServerTransformPlugin } from '../transform' import { serviceWorkerPlugin } from './serverPluginServiceWorker' +import { proxyPlugin } from './serverPluginProxy' export { rewriteImports } from './serverPluginModuleRewrite' @@ -56,6 +57,7 @@ export function createServer(config: ServerConfig = {}): Server { const resolvedPlugins = [ ...plugins, + proxyPlugin, serviceWorkerPlugin, hmrPlugin, moduleRewritePlugin, diff --git a/src/node/server/serverPluginProxy.ts b/src/node/server/serverPluginProxy.ts new file mode 100644 index 00000000000000..5c301fd21bccde --- /dev/null +++ b/src/node/server/serverPluginProxy.ts @@ -0,0 +1,27 @@ +import { ServerPlugin } from '.' +import { URL } from 'url' + +export const proxyPlugin: ServerPlugin = ({ app, config }) => { + if (!config.proxy) { + return + } + + const debug = require('debug')('vite:proxy') + const proxy = require('koa-proxies') + const options = config.proxy + Object.keys(options).forEach((path) => { + let opts = options[path] + if (typeof opts === 'string') { + opts = { target: opts } + } + opts.logs = (ctx, target) => { + debug( + `${ctx.req.method} ${(ctx.req as any).oldPath} proxy to -> ${new URL( + ctx.req.url!, + target + )}` + ) + } + app.use(proxy(path, opts)) + }) +} diff --git a/yarn.lock b/yarn.lock index a3731abc006f63..b97a49adefd970 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2257,7 +2257,7 @@ debug@^2.2.0, debug@^2.3.3: dependencies: ms "2.0.0" -debug@^3.1.0: +debug@^3.0.0, debug@^3.1.0: version "3.2.6" resolved "https://registry.yarnpkg.com/debug/-/debug-3.2.6.tgz#e83d17de16d8a7efb7717edbe5fb10135eee629b" integrity sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ== @@ -2599,6 +2599,11 @@ etag@^1.3.0, etag@^1.8.1: resolved "https://registry.yarnpkg.com/etag/-/etag-1.8.1.tgz#41ae2eeb65efa62268aebfea83ac7d79299b0887" integrity sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc= +eventemitter3@^4.0.0: + version "4.0.4" + resolved "https://registry.yarnpkg.com/eventemitter3/-/eventemitter3-4.0.4.tgz#b5463ace635a083d018bdc7c917b4c5f10a85384" + integrity sha512-rlaVLnVxtxvoyLsQQFBx53YmXHDxRIzzTLbdfxqi4yocpSjAxXwkU0cScM5JgSKMqEhrZpnvQ2D9gjylR0AimQ== + exec-sh@^0.3.2: version "0.3.4" resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.4.tgz#3a018ceb526cc6f6df2bb504b2bfe8e3a4934ec5" @@ -2842,6 +2847,13 @@ find-up@^4.0.0, find-up@^4.1.0: locate-path "^5.0.0" path-exists "^4.0.0" +follow-redirects@^1.0.0: + version "1.11.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.11.0.tgz#afa14f08ba12a52963140fe43212658897bc0ecb" + integrity sha512-KZm0V+ll8PfBrKwMzdo5D13b1bur9Iq9Zd/RMmAoQQcl2PxxFml8cxXPaaPYVbV0RjNjq1CU7zIzAOqtUPudmA== + dependencies: + debug "^3.0.0" + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" @@ -3191,6 +3203,14 @@ http-errors@^1.6.3, http-errors@~1.7.2: statuses ">= 1.5.0 < 2" toidentifier "1.0.0" +http-errors@~1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.4.0.tgz#6c0242dea6b3df7afda153c71089b31c6e82aabf" + integrity sha1-bAJC3qaz33r9oVPHEImzHG6Cqr8= + dependencies: + inherits "2.0.1" + statuses ">= 1.2.1 < 2" + http-errors@~1.6.2: version "1.6.3" resolved "https://registry.yarnpkg.com/http-errors/-/http-errors-1.6.3.tgz#8b55680bb4be283a0b5bf4ea2e38580be1d9320d" @@ -3201,6 +3221,15 @@ http-errors@~1.6.2: setprototypeof "1.1.0" statuses ">= 1.4.0 < 2" +http-proxy@^1.16.2: + version "1.18.1" + resolved "https://registry.yarnpkg.com/http-proxy/-/http-proxy-1.18.1.tgz#401541f0534884bbf95260334e72f88ee3976549" + integrity sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ== + dependencies: + eventemitter3 "^4.0.0" + follow-redirects "^1.0.0" + requires-port "^1.0.0" + http-signature@~1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1" @@ -3318,6 +3347,11 @@ inherits@2, inherits@2.0.4, inherits@^2.0.3, inherits@^2.0.4, inherits@~2.0.3: resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c" integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ== +inherits@2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.1.tgz#b17d08d326b4423e568eff719f91b0b1cbdf69f1" + integrity sha1-sX0I0ya0Qj5Wjv9xn5GwscvfafE= + inherits@2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" @@ -3630,6 +3664,11 @@ is-wsl@^2.1.1: dependencies: is-docker "^2.0.0" +isarray@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-0.0.1.tgz#8a18acfca9a8f4177e09abfc6038939b05d1eedf" + integrity sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8= + isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" @@ -4273,6 +4312,14 @@ koa-etag@^3.0.0: etag "^1.3.0" mz "^2.1.0" +koa-proxies@^0.11.0: + version "0.11.0" + resolved "https://registry.yarnpkg.com/koa-proxies/-/koa-proxies-0.11.0.tgz#43dde4260080f7cb0f284655f85cf654bbe9ec84" + integrity sha512-iXGRADBE0fM7g7AttNOlLZ/cCFKXeVMHbFJKIRb0dUCrSYXi02loyVSdBlKlBQ5ZfVKJLo9Q9FyqwVTp1poVVA== + dependencies: + http-proxy "^1.16.2" + path-match "^1.2.4" + koa-send@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/koa-send/-/koa-send-5.0.0.tgz#5e8441e07ef55737734d7ced25b842e50646e7eb" @@ -5197,11 +5244,26 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375" integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q== +path-match@^1.2.4: + version "1.2.4" + resolved "https://registry.yarnpkg.com/path-match/-/path-match-1.2.4.tgz#a62747f3c7e0c2514762697f24443585b09100ea" + integrity sha1-pidH88fgwlFHYml/JEQ1hbCRAOo= + dependencies: + http-errors "~1.4.0" + path-to-regexp "^1.0.0" + path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== +path-to-regexp@^1.0.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-1.8.0.tgz#887b3ba9d84393e87a0a0b9f4cb756198b53548a" + integrity sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA== + dependencies: + isarray "0.0.1" + path-type@^1.0.0: version "1.1.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" @@ -6123,6 +6185,11 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== +requires-port@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff" + integrity sha1-kl0mAdOaxIXgkc8NpcbmlNw9yv8= + resolve-cwd@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d" @@ -6587,7 +6654,7 @@ static-extend@^0.1.1: define-property "^0.2.5" object-copy "^0.1.0" -"statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0: +"statuses@>= 1.2.1 < 2", "statuses@>= 1.4.0 < 2", "statuses@>= 1.5.0 < 2", statuses@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c" integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=